From 6965971f61555bb97936acc397c8a45b6a06e556 Mon Sep 17 00:00:00 2001 From: rkatsyuryna Date: Tue, 24 Nov 2015 19:12:19 +0200 Subject: [PATCH 001/226] Requester should be able to wait for acks even if configured to waitForResponses:0 --- .../github/tcdl/msb/api/RequestOptions.java | 10 +- .../github/tcdl/msb/collector/Collector.java | 134 +++++++++--------- .../github/tcdl/msb/impl/RequesterImpl.java | 12 +- .../tcdl/msb/api/RequestOptionsTest.java | 36 ----- .../tcdl/msb/collector/CollectorTest.java | 40 +++--- .../tcdl/msb/impl/RequesterImplTest.java | 40 +++--- 6 files changed, 127 insertions(+), 145 deletions(-) delete mode 100644 core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java index b25ea36d..aa2eec3a 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java +++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java @@ -43,15 +43,7 @@ public Integer getResponseTimeout() { } public Integer getWaitForResponses() { - if (waitForResponses == null) { - return 0; - } else { - return waitForResponses; - } - } - - public boolean isWaitForResponses() { - return getWaitForResponses() != 0; + return waitForResponses; } public MessageTemplate getMessageTemplate() { diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index 87b6687a..a2fd5a72 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -1,5 +1,16 @@ package io.github.tcdl.msb.collector; +import static io.github.tcdl.msb.support.Utils.ifNull; +import java.time.Clock; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ScheduledFuture; + import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -13,18 +24,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Clock; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ScheduledFuture; - -import static io.github.tcdl.msb.support.Utils.ifNull; - /** * {@link Collector} is a component which collects responses and acknowledgements for sent requests. */ @@ -42,7 +41,8 @@ public class Collector { private int timeoutMs; private int currentTimeoutMs; - private long waitForAcksUntil; + private Integer waitForAcksMs; + private Long waitForAcksUntil; private int waitForResponses; private TypeReference payloadTypeReference; private int responsesRemaining; @@ -62,9 +62,9 @@ public class Collector { private ScheduledFuture ackTimeoutFuture; private ScheduledFuture responseTimeoutFuture; private CollectorManager collectorManager; - private boolean shouldWaitUntilResponseTimeout; - public Collector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl msbContext, EventHandlers eventHandlers, TypeReference payloadTypeReference) { + public Collector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl msbContext, EventHandlers eventHandlers, + TypeReference payloadTypeReference) { this.requestMessage = requestMessage; this.clock = msbContext.getClock(); @@ -78,14 +78,15 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt this.timeoutMsById = new HashMap<>(); this.responsesRemainingById = new HashMap<>(); + this.waitForAcksMs = requestOptions.getAckTimeout(); + this.waitForAcksUntil = null; + this.waitForResponses = getWaitForResponsesFromConfigs(requestOptions); + this.timeoutMs = getResponseTimeoutFromConfigs(requestOptions); this.currentTimeoutMs = timeoutMs; - this.waitForAcksUntil = getWaitForAckUntilFromConfigs(requestOptions); - this.waitForResponses = requestOptions.getWaitForResponses(); this.responsesRemaining = waitForResponses; this.payloadTypeReference = payloadTypeReference; - this.shouldWaitUntilResponseTimeout = requestOptions.getWaitForResponses() == WAIT_FOR_RESPONSES_UNTIL_TIMEOUT; if (eventHandlers != null) { onRawResponse = Optional.ofNullable(eventHandlers.onRawResponse()); @@ -96,14 +97,17 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt } boolean isAwaitingAcks() { - return waitForAcksUntil > clock.instant().toEpochMilli(); + return this.waitForAcksUntil != null && waitForAcksUntil > clock.instant().toEpochMilli(); } - public boolean isAwaitingResponses() { - return getResponsesRemaining() > 0 || shouldWaitUntilResponseTimeout; + boolean isAwaitingResponses() { + return getResponsesRemaining() > 0; } public void listenForResponses() { + if (this.waitForAcksMs != null && this.waitForAcksMs != 0) { + this.waitForAcksUntil = this.startedAt + waitForAcksMs; + } collectorManager.registerCollector(this); } @@ -128,9 +132,8 @@ public void handleMessage(Message incomingMessage) { processAck(incomingMessage.getAck()); - if (isAwaitingResponses()) { + if (isAwaitingResponses()) return; - } //set ack timer task in case we received ALL expected responses but still have to wait for ack if (isAwaitingAcks()) { @@ -151,21 +154,6 @@ protected void end() { onEnd.ifPresent(handler -> handler.call(null)); } - public void waitForResponses() { - LOG.debug("Waiting for responses until {}.", clock.instant().plus(currentTimeoutMs, ChronoUnit.MILLIS)); - this.responseTimeoutFuture = timeoutManager.enableResponseTimeout(this.currentTimeoutMs, this); - } - - private void waitForAcks() { - if (ackTimeoutFuture == null) { - LOG.debug("Waiting for ack until {}.", Instant.ofEpochMilli(this.waitForAcksUntil)); - long ackTimeoutMs = waitForAcksUntil - clock.instant().toEpochMilli(); - ackTimeoutFuture = timeoutManager.enableAckTimeout(ackTimeoutMs, this); - } else { - LOG.debug("Ack timeout is already scheduled"); - } - } - private void processAck(Acknowledge acknowledge) { if (acknowledge == null) return; @@ -198,19 +186,6 @@ private Integer setTimeoutMsForResponderId(String responderId, Integer timeoutMs return timeoutMs; } - int getResponsesRemaining() { - if (responsesRemainingById == null || responsesRemainingById.isEmpty()) { - return responsesRemaining; - } - - Integer sumOfResponsesRemaining = 0; - for (Integer responses : responsesRemainingById.values()) { - sumOfResponsesRemaining += responses; - } - - return Math.max(responsesRemaining, sumOfResponsesRemaining); - } - private int getMaxTimeoutMs() { if (timeoutMsById.isEmpty()) { return this.timeoutMs; @@ -232,17 +207,27 @@ private Integer incResponsesRemaining(Integer inc) { return (responsesRemaining = Math.max(responsesRemaining + inc, 0)); } - private int setResponsesRemainingForResponderId(String responderId, Integer responsesRemaining) { - boolean notChanged = (responsesRemainingById != null && responsesRemainingById.containsKey(responderId) && responsesRemainingById - .get(responderId).equals(responsesRemaining)); - if (notChanged) - return 0; + int getResponsesRemaining() { + if (responsesRemainingById.isEmpty()) { + return responsesRemaining; + } + + Integer sumOfResponsesRemaining = 0; + for (Integer responses : responsesRemainingById.values()) { + sumOfResponsesRemaining += responses; + } + return Math.max(responsesRemaining, sumOfResponsesRemaining); + } + + private Integer setResponsesRemainingForResponderId(String responderId, int responsesRemaining) { + //check for responsesRemaining < 0 seems redundant, since if config.waitForResponses == -1 we use Infinity boolean atMin = (responsesRemaining < 0 && (responsesRemainingById.isEmpty() || !responsesRemainingById .containsKey(responderId))); - if (atMin) - return 0; - + if (atMin) { + return null; + } + //when second, third, etc time same value (not equals 0) for responsesRemaining is received for corresponding responderId, it must be sum up with previous. if (responsesRemaining == 0) { responsesRemainingById.put(responderId, 0); } else { @@ -253,6 +238,30 @@ private int setResponsesRemainingForResponderId(String responderId, Integer resp return responsesRemainingById.get(responderId); } + private int getWaitForResponsesFromConfigs(RequestOptions requestOptions) { + if (requestOptions.getWaitForResponses() == null || requestOptions.getWaitForResponses() == WAIT_FOR_RESPONSES_UNTIL_TIMEOUT) { + // use for Infinity number or expected responses + return Integer.MAX_VALUE; + } else { + return requestOptions.getWaitForResponses(); + } + } + + public void waitForResponses() { + LOG.debug("Waiting for responses until {}.", clock.instant().plus(currentTimeoutMs, ChronoUnit.MILLIS)); + this.responseTimeoutFuture = timeoutManager.enableResponseTimeout(this.currentTimeoutMs, this); + } + + private void waitForAcks() { + if (ackTimeoutFuture == null) { + LOG.debug("Waiting for ack until {}.", Instant.ofEpochMilli(this.waitForAcksUntil)); + long ackTimeoutMs = waitForAcksUntil - clock.instant().toEpochMilli(); + ackTimeoutFuture = timeoutManager.enableAckTimeout(ackTimeoutMs, this); + } else { + LOG.debug("Ack timeout is already scheduled"); + } + } + private int getResponseTimeoutFromConfigs(RequestOptions requestOptions) { if (requestOptions.getResponseTimeout() == null) { return 3000; @@ -260,13 +269,6 @@ private int getResponseTimeoutFromConfigs(RequestOptions requestOptions) { return requestOptions.getResponseTimeout(); } - private long getWaitForAckUntilFromConfigs(RequestOptions requestOptions) { - if (requestOptions.getAckTimeout() == null) { - return 0; - } - return this.startedAt + requestOptions.getAckTimeout(); - } - private void cancelResponseTimeoutTask() { if (this.responseTimeoutFuture != null) { responseTimeoutFuture.cancel(true); diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java index ecb932d8..aec4f150 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java @@ -15,7 +15,7 @@ /** * Implementation of {@link Requester} - * + * * Expected responses are matched by correlationId from original request. * * @see Requester @@ -93,7 +93,7 @@ public void publish(Object requestPayload, Message originalMessage, String tag) Message message = messageFactory.createRequestMessage(messageBuilder, requestPayload); //use Collector instance to handle expected responses/acks - if (requestOptions.isWaitForResponses()) { + if (isWaitForAckMs() || isWaitForResponses()) { String topic = message.getTopics().getResponse(); Collector collector = createCollector(topic, message, requestOptions, context, eventHandlers); @@ -109,6 +109,14 @@ public void publish(Object requestPayload, Message originalMessage, String tag) } } + private boolean isWaitForAckMs() { + return requestOptions.getAckTimeout() != null && requestOptions.getAckTimeout() != 0; + } + + private boolean isWaitForResponses() { + return requestOptions.getWaitForResponses() != null && requestOptions.getWaitForResponses() != 0; + } + /** * {@inheritDoc} */ diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java deleted file mode 100644 index 61e4a894..00000000 --- a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.tcdl.msb.api; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public class RequestOptionsTest { - - @Test - public void testGetWaitForResponsesConfigsNull() { - RequestOptions requestOptions = new RequestOptions.Builder() - .withWaitForResponses(null) - .build(); - - assertEquals("expect 0 if MessageOptions.waitForResponses is null", Integer.valueOf(0), requestOptions.getWaitForResponses()); - } - - @Test - public void testGetWaitForResponsesConfigsMinusOne() { - RequestOptions requestOptions = new RequestOptions.Builder() - .withWaitForResponses(-1) - .build(); - - assertEquals("expect -1 if MessageOptions.waitForResponses is -1", Integer.valueOf(-1), requestOptions.getWaitForResponses()); - } - - @Test - public void testGetWaitForResponsesConfigsPositive() { - RequestOptions requestOptions = new RequestOptions.Builder() - .withWaitForResponses(100) - .build(); - - assertEquals("expect 100 if MessageOptions.waitForResponses is 100", Integer.valueOf(100), requestOptions.getWaitForResponses()); - } - -} diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java index 51be49a7..8aee98db 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java @@ -35,9 +35,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -/** - * Created by rdro on 4/27/2015. - */ + @RunWith(MockitoJUnitRunner.class) public class CollectorTest { @@ -88,45 +86,55 @@ public void setUp() throws IOException { } @Test - public void testGetWaitForResponsesConfigsReturnFalse() { + public void testIsAwaitingResponsesConfigsReturnNull() { + when(requestOptionsMock.getWaitForResponses()).thenReturn(null); + Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + assertTrue("expect true if MessageOptions.waitForResponses is null", collector.isAwaitingResponses()); + } + + @Test + public void testIsAwaitingResponsesConfigsReturnZero() { when(requestOptionsMock.getWaitForResponses()).thenReturn(0); Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); assertFalse("expect false if MessageOptions.waitForResponses equals 0", collector.isAwaitingResponses()); } @Test - public void testGetWaitForResponsesConfigsReturnFalseMinusCase() { - when(requestOptionsMock.getWaitForResponses()).thenReturn(-10); + public void testIsAwaitingResponsesConfigsReturnMinusOne() { + when(requestOptionsMock.getWaitForResponses()).thenReturn(-1); Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); - assertFalse("expect false if MessageOptions.waitForResponses equals -10", collector.isAwaitingResponses()); + assertTrue("expect true if MessageOptions.waitForResponses equals -1", collector.isAwaitingResponses()); } @Test - public void testGetWaitForResponsesConfigsReturnTrue() { + public void testIsAwaitingResponsesConfigsReturnPositive() { when(requestOptionsMock.getWaitForResponses()).thenReturn(100); Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); assertTrue("expect true if MessageOptions.waitForResponses equals 100", collector.isAwaitingResponses()); } @Test - public void testGetWaitForResponsesConfigsReturnTrueMinusOne() { - when(requestOptionsMock.getWaitForResponses()).thenReturn(-1); + public void testIsAwaitingAcksConfigsReturnPositiveValue() { + when(requestOptionsMock.getAckTimeout()).thenReturn(1000); Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); - assertTrue("expect true if MessageOptions.waitForResponses equals -1", collector.isAwaitingResponses()); + collector.listenForResponses(); + assertTrue("expect true if MessageOptions.ackTimeout equals 1000", collector.isAwaitingAcks()); } @Test - public void testIsAwaitingAcksConfigsNotSetAckTimeoutReturnFalse() { + public void testIsAwaitingAcksConfigsReturnNull() { when(requestOptionsMock.getAckTimeout()).thenReturn(null); Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + collector.listenForResponses(); assertFalse("expect false if MessageOptions.ackTimeout null", collector.isAwaitingAcks()); } @Test - public void testIsAwaitingAcksReturnTrue() { - when(requestOptionsMock.getAckTimeout()).thenReturn(200); + public void testIsAwaitingAcksConfigsReturnZero() { + when(requestOptionsMock.getAckTimeout()).thenReturn(0); Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); - assertTrue("expect true if MessageOptions.ackTimeout equals 200", collector.isAwaitingAcks()); + collector.listenForResponses(); + assertFalse("expect false if MessageOptions.ackTimeout=0", collector.isAwaitingAcks()); } @Test @@ -447,7 +455,7 @@ public void testHandleResponseReceivedAcksWithUpdatedTimeoutAndResponsesRemainin int timeoutMs = 50; int timeoutMsInAckResponderOne = 100; int responsesRemainingResponderOne = 5; - int timeoutMsInAckResponderTwo = 222; + int timeoutMsInAckResponderTwo = 200; int responsesRemainingResponderTwo = 7; when(requestOptionsMock.getAckTimeout()).thenReturn(0); when(requestOptionsMock.getResponseTimeout()).thenReturn(timeoutMs); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index 73a84060..b14dae7a 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; @@ -43,9 +44,6 @@ public class RequesterImplTest { private static final String NAMESPACE = "test:requester"; - @Mock - private EventHandlers eventHandlerMock; - @Mock private ChannelManager channelManagerMock; @@ -60,7 +58,7 @@ public class RequesterImplTest { @Test public void testPublishNoWaitForResponses() throws Exception { - RequesterImpl requester = initRequesterForResponsesWithTimeout(0); + RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null); requester.publish(TestUtils.createSimpleRequestPayload()); @@ -70,9 +68,7 @@ public void testPublishNoWaitForResponses() throws Exception { @Test public void testPublishWaitForResponses() throws Exception { - RequesterImpl requester = initRequesterForResponsesWithTimeout(1); - - //doReturn(mock(CollectorManager.class)).when(collectorMock).findOrCreateCollectorManager(anyString()); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null); requester.publish(TestUtils.createSimpleRequestPayload()); @@ -80,10 +76,21 @@ public void testPublishWaitForResponses() throws Exception { verify(collectorMock).waitForResponses(); } + @Test + public void testPublishWaitForResponsesAck() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, arg -> fail()); + + requester.publish(TestUtils.createSimpleRequestPayload()); + + Message responseMessage = TestUtils.createMsbRequestMessage("some:topic", "body text"); + collectorMock.handleMessage(responseMessage); + + } + @Test public void testProducerPublishWithPayload() throws Exception { String bodyText = "Body text"; - RequesterImpl requester = initRequesterForResponsesWithTimeout(0); + RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); RestPayload payload = TestUtils.createPayloadWithTextBody(bodyText); @@ -97,7 +104,7 @@ public void testProducerPublishWithPayload() throws Exception { @SuppressWarnings("unchecked") public void testAcknowledgeEventHandlerIsAdded() throws Exception { Callback onAckMock = mock(Callback.class); - RequesterImpl requester = initRequesterForResponsesWithTimeout(1); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null); requester.onAcknowledge(onAckMock); @@ -111,7 +118,7 @@ public void testAcknowledgeEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testResponseEventHandlerIsAdded() throws Exception { Callback onResponseMock = mock(Callback.class); - RequesterImpl requester = initRequesterForResponsesWithTimeout(1); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null); requester.onResponse(onResponseMock); @@ -126,7 +133,7 @@ public void testResponseEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testRawResponseEventHandlerIsAdded() throws Exception { Callback onRawResponseMock = mock(Callback.class); - RequesterImpl requester = initRequesterForResponsesWithTimeout(1); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null); requester.onRawResponse(onRawResponseMock); @@ -141,7 +148,7 @@ public void testRawResponseEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testEndEventHandlerIsAdded() throws Exception { Callback onEndMock = mock(Callback.class); - RequesterImpl requester = initRequesterForResponsesWithTimeout(1); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null); requester.onEnd(onEndMock); @@ -155,7 +162,7 @@ public void testEndEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testNoEventHandlerAdded() throws Exception { Callback onEndMock = mock(Callback.class); - RequesterImpl requester = initRequesterForResponsesWithTimeout(1); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null); assertThat(requester.eventHandlers.onAcknowledge(), not(onEndMock)); assertThat(requester.eventHandlers.onResponse(), not(onEndMock)); @@ -211,12 +218,12 @@ public void testRequestMessageWithTags() throws Exception { assertArrayEquals(new String[]{tag, dynamicTag}, requestMessage.getTags().toArray()); } - private RequesterImpl initRequesterForResponsesWithTimeout(int numberOfResponses) throws Exception { + private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout, Integer ackTimeout , Callback endHandler) throws Exception { MessageTemplate messageTemplateMock = mock(MessageTemplate.class); RequestOptions requestOptionsMock = new RequestOptions.Builder().withMessageTemplate(messageTemplateMock).withWaitForResponses(numberOfResponses) - .withResponseTimeout(100).build(); + .withResponseTimeout(respTimeout).withAckTimeout(ackTimeout).build(); when(channelManagerMock.findOrCreateProducer(anyString())).thenReturn(producerMock); @@ -225,8 +232,9 @@ private RequesterImpl initRequesterForResponsesWithTimeout(int numb .build(); RequesterImpl requester = spy(RequesterImpl.create(NAMESPACE, requestOptionsMock, msbContext, new TypeReference() {})); + requester.onEnd(endHandler); - collectorMock = spy(new Collector<>(NAMESPACE, TestUtils.createMsbRequestMessageNoPayload(NAMESPACE), requestOptionsMock, msbContext, eventHandlerMock, + collectorMock = spy(new Collector<>(NAMESPACE, TestUtils.createMsbRequestMessageNoPayload(NAMESPACE), requestOptionsMock, msbContext, requester.eventHandlers, new TypeReference() {})); doReturn(collectorMock) From dc8e6a242f0affee4eaba1a3a09fb0fe67ab7bad Mon Sep 17 00:00:00 2001 From: rkatsyuryna Date: Thu, 26 Nov 2015 16:16:40 +0200 Subject: [PATCH 002/226] Add fixes for Review comments and improvement from msb --- .../github/tcdl/msb/api/RequestOptions.java | 11 +- .../github/tcdl/msb/collector/Collector.java | 74 ++-- .../tcdl/msb/collector/TimeoutManager.java | 7 +- .../github/tcdl/msb/impl/RequesterImpl.java | 2 +- .../tcdl/msb/api/RequestOptionsTest.java | 37 ++ .../tcdl/msb/collector/CollectorTest.java | 379 ++++++++++++++---- .../tcdl/msb/impl/RequesterImplTest.java | 9 +- .../io/github/tcdl/msb/support/TestUtils.java | 16 + pom.xml | 10 + 9 files changed, 429 insertions(+), 116 deletions(-) create mode 100644 core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java index aa2eec3a..f933144c 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java +++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java @@ -5,6 +5,8 @@ */ public class RequestOptions { + public static final int WAIT_FOR_RESPONSES_UNTIL_TIMEOUT = -1; + /** * Min time (in milliseconds) to wait for acknowledgements. */ @@ -42,8 +44,13 @@ public Integer getResponseTimeout() { return responseTimeout; } - public Integer getWaitForResponses() { - return waitForResponses; + public int getWaitForResponses() { + if (waitForResponses == null || waitForResponses == -1) { + // use for Infinity number or expected responses + return WAIT_FOR_RESPONSES_UNTIL_TIMEOUT; + } else { + return waitForResponses; + } } public MessageTemplate getMessageTemplate() { diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index a2fd5a72..3261cfa3 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -1,6 +1,7 @@ package io.github.tcdl.msb.collector; import static io.github.tcdl.msb.support.Utils.ifNull; +import static java.lang.Math.toIntExact; import java.time.Clock; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -31,8 +32,6 @@ public class Collector { private static final Logger LOG = LoggerFactory.getLogger(Collector.class); - private static final int WAIT_FOR_RESPONSES_UNTIL_TIMEOUT = -1; - private List ackMessages; private List payloadMessages; @@ -42,12 +41,14 @@ public class Collector { private int timeoutMs; private int currentTimeoutMs; private Integer waitForAcksMs; - private Long waitForAcksUntil; - private int waitForResponses; - private TypeReference payloadTypeReference; + private Instant waitForAcksUntil; + private int responsesRemaining; + private boolean shouldWaitUntilResponseTimeout; + + private TypeReference payloadTypeReference; - private Long startedAt; + private long startedAt; private TimeoutManager timeoutManager; private ObjectMapper payloadMapper; @@ -80,12 +81,18 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt this.waitForAcksMs = requestOptions.getAckTimeout(); this.waitForAcksUntil = null; - this.waitForResponses = getWaitForResponsesFromConfigs(requestOptions); this.timeoutMs = getResponseTimeoutFromConfigs(requestOptions); this.currentTimeoutMs = timeoutMs; + int waitForResponses = requestOptions.getWaitForResponses(); + this.responsesRemaining = waitForResponses; + + if (waitForResponses == RequestOptions.WAIT_FOR_RESPONSES_UNTIL_TIMEOUT) { + shouldWaitUntilResponseTimeout = true; + } + this.payloadTypeReference = payloadTypeReference; if (eventHandlers != null) { @@ -97,16 +104,16 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt } boolean isAwaitingAcks() { - return this.waitForAcksUntil != null && waitForAcksUntil > clock.instant().toEpochMilli(); + return this.waitForAcksUntil != null && waitForAcksUntil.isAfter(Instant.now()); } boolean isAwaitingResponses() { - return getResponsesRemaining() > 0; + return shouldWaitUntilResponseTimeout || getResponsesRemaining() > 0; } public void listenForResponses() { if (this.waitForAcksMs != null && this.waitForAcksMs != 0) { - this.waitForAcksUntil = this.startedAt + waitForAcksMs; + this.waitForAcksUntil = Instant.ofEpochMilli(this.startedAt).plusMillis(waitForAcksMs); } collectorManager.registerCollector(this); } @@ -154,27 +161,24 @@ protected void end() { onEnd.ifPresent(handler -> handler.call(null)); } - private void processAck(Acknowledge acknowledge) { + void processAck(Acknowledge acknowledge) { if (acknowledge == null) return; - if (acknowledge.getTimeoutMs() != null && acknowledge.getResponderId() != null) { - Integer newTimeoutMs = setTimeoutMsForResponderId(acknowledge.getResponderId(), acknowledge.getTimeoutMs()); - if (newTimeoutMs != null) { - int prevTimeoutMs = this.currentTimeoutMs; - - this.currentTimeoutMs = getMaxTimeoutMs(); - if (prevTimeoutMs != currentTimeoutMs) { - cancelResponseTimeoutTask(); - this.responseTimeoutFuture = timeoutManager.enableResponseTimeout(this.currentTimeoutMs, this); - } - } - } - if (acknowledge.getResponsesRemaining() != null) { LOG.debug("Responses remaining for responderId [{}] is set to {}", acknowledge.getResponderId(), setResponsesRemainingForResponderId(acknowledge.getResponderId(), acknowledge.getResponsesRemaining())); } + + if (acknowledge.getTimeoutMs() != null) { + setTimeoutMsForResponderId(acknowledge.getResponderId(), acknowledge.getTimeoutMs()); + } + + Integer newTimeoutMs = getMaxTimeoutMs(); + if (newTimeoutMs != this.currentTimeoutMs) { + this.currentTimeoutMs = newTimeoutMs; + waitForResponses(); + } } private Integer setTimeoutMsForResponderId(String responderId, Integer timeoutMs) { @@ -238,25 +242,17 @@ private Integer setResponsesRemainingForResponderId(String responderId, int resp return responsesRemainingById.get(responderId); } - private int getWaitForResponsesFromConfigs(RequestOptions requestOptions) { - if (requestOptions.getWaitForResponses() == null || requestOptions.getWaitForResponses() == WAIT_FOR_RESPONSES_UNTIL_TIMEOUT) { - // use for Infinity number or expected responses - return Integer.MAX_VALUE; - } else { - return requestOptions.getWaitForResponses(); - } - } - public void waitForResponses() { - LOG.debug("Waiting for responses until {}.", clock.instant().plus(currentTimeoutMs, ChronoUnit.MILLIS)); - this.responseTimeoutFuture = timeoutManager.enableResponseTimeout(this.currentTimeoutMs, this); + int newTimeoutMs = this.currentTimeoutMs - toIntExact(clock.instant().toEpochMilli() - this.startedAt); + LOG.debug("Waiting for responses until {}.", clock.instant().plus(newTimeoutMs, ChronoUnit.MILLIS)); + this.responseTimeoutFuture = timeoutManager.enableResponseTimeout(newTimeoutMs, this); } - private void waitForAcks() { + void waitForAcks() { if (ackTimeoutFuture == null) { - LOG.debug("Waiting for ack until {}.", Instant.ofEpochMilli(this.waitForAcksUntil)); - long ackTimeoutMs = waitForAcksUntil - clock.instant().toEpochMilli(); - ackTimeoutFuture = timeoutManager.enableAckTimeout(ackTimeoutMs, this); + LOG.debug("Waiting for ack until {}.", this.waitForAcksUntil); + long ackTimeoutMs = waitForAcksUntil.toEpochMilli() - clock.instant().toEpochMilli(); + ackTimeoutFuture = timeoutManager.enableAckTimeout(toIntExact(ackTimeoutMs), this); } else { LOG.debug("Ack timeout is already scheduled"); } diff --git a/core/src/main/java/io/github/tcdl/msb/collector/TimeoutManager.java b/core/src/main/java/io/github/tcdl/msb/collector/TimeoutManager.java index 5e107e35..a7a3c9e8 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/TimeoutManager.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/TimeoutManager.java @@ -25,6 +25,11 @@ public TimeoutManager(int threadPoolSize) { protected ScheduledFuture enableResponseTimeout(int timeoutMs, Collector collector) { LOG.debug("Enabling response timeout for {} ms", timeoutMs); + if (timeoutMs <= 0) { + LOG.debug("Unable to schedule timeout with negative delay : {}", timeoutMs); + return null; + } + try { return timeoutExecutorDecorator.schedule(() -> { LOG.debug("Response timeout expired."); @@ -37,7 +42,7 @@ protected ScheduledFuture enableResponseTimeout(int timeoutMs, Collector coll } - protected ScheduledFuture enableAckTimeout(long timeoutMs, Collector collector) { + protected ScheduledFuture enableAckTimeout(int timeoutMs, Collector collector) { LOG.debug("Enabling ack timeout for {} ms", timeoutMs); if (timeoutMs <= 0) { diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java index aec4f150..0a6967b8 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java @@ -114,7 +114,7 @@ private boolean isWaitForAckMs() { } private boolean isWaitForResponses() { - return requestOptions.getWaitForResponses() != null && requestOptions.getWaitForResponses() != 0; + return requestOptions.getWaitForResponses() != 0; } /** diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java new file mode 100644 index 00000000..1641c31c --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java @@ -0,0 +1,37 @@ +package io.github.tcdl.msb.api; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class RequestOptionsTest { + + @Test + public void testGetWaitForResponsesConfigsNull() { + RequestOptions requestOptions = new RequestOptions.Builder() + .withWaitForResponses(null) + .build(); + + assertEquals( RequestOptions.WAIT_FOR_RESPONSES_UNTIL_TIMEOUT, requestOptions.getWaitForResponses()); + } + + @Test + public void testGetWaitForResponsesConfigsMinusOne() { + RequestOptions requestOptions = new RequestOptions.Builder() + .withWaitForResponses(-1) + .build(); + + assertEquals(RequestOptions.WAIT_FOR_RESPONSES_UNTIL_TIMEOUT, requestOptions.getWaitForResponses()); + } + + @Test + public void testGetWaitForResponsesConfigsPositive() { + int responsesRemaining = 100; + RequestOptions requestOptions = new RequestOptions.Builder() + .withWaitForResponses(responsesRemaining) + .build(); + + assertEquals(responsesRemaining, requestOptions.getWaitForResponses()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java index 8aee98db..5a435650 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java @@ -1,9 +1,31 @@ package io.github.tcdl.msb.collector; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import java.io.IOException; +import java.time.Clock; +import java.util.concurrent.ScheduledFuture; + +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.RequestOptions; +import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.Message; @@ -11,31 +33,17 @@ import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.events.EventHandlers; import io.github.tcdl.msb.impl.MsbContextImpl; +import io.github.tcdl.msb.impl.ResponderImpl; import io.github.tcdl.msb.message.MessageFactory; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import java.io.IOException; -import java.time.Clock; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - - @RunWith(MockitoJUnitRunner.class) public class CollectorTest { @@ -73,7 +81,7 @@ public class CollectorTest { @Before public void setUp() throws IOException { - msbContext = TestUtils.createMsbContextBuilder() + this.msbContext = TestUtils.createMsbContextBuilder() .withMsbConfigurations(msbConfigurationsMock) .withMessageFactory(messageFactoryMock) .withChannelManager(channelManagerMock) @@ -83,63 +91,63 @@ public void setUp() throws IOException { .build(); when(collectorManagerFactoryMock.findOrCreateCollectorManager(TOPIC)).thenReturn(collectorManagerMock); - } - @Test - public void testIsAwaitingResponsesConfigsReturnNull() { - when(requestOptionsMock.getWaitForResponses()).thenReturn(null); - Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); - assertTrue("expect true if MessageOptions.waitForResponses is null", collector.isAwaitingResponses()); - } - - @Test - public void testIsAwaitingResponsesConfigsReturnZero() { - when(requestOptionsMock.getWaitForResponses()).thenReturn(0); - Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); - assertFalse("expect false if MessageOptions.waitForResponses equals 0", collector.isAwaitingResponses()); } @Test public void testIsAwaitingResponsesConfigsReturnMinusOne() { when(requestOptionsMock.getWaitForResponses()).thenReturn(-1); - Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); + assertTrue("expect true if MessageOptions.waitForResponses equals -1", collector.isAwaitingResponses()); } @Test public void testIsAwaitingResponsesConfigsReturnPositive() { - when(requestOptionsMock.getWaitForResponses()).thenReturn(100); - Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); - assertTrue("expect true if MessageOptions.waitForResponses equals 100", collector.isAwaitingResponses()); + when(requestOptionsMock.getWaitForResponses()).thenReturn(1000); + Collector collector = createCollector(); + + assertTrue("expect true if MessageOptions.waitForResponses is positive number", collector.isAwaitingResponses()); + } + + @Test + public void testIsAwaitingResponsesConfigsReturnZero() { + when(requestOptionsMock.getWaitForResponses()).thenReturn(0); + Collector collector = createCollector(); + + assertFalse("expect false if MessageOptions.waitForResponses equals 0", collector.isAwaitingResponses()); } @Test public void testIsAwaitingAcksConfigsReturnPositiveValue() { when(requestOptionsMock.getAckTimeout()).thenReturn(1000); - Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); - assertTrue("expect true if MessageOptions.ackTimeout equals 1000", collector.isAwaitingAcks()); + + assertTrue("expect true if MessageOptions.ackTimeout is positive number", collector.isAwaitingAcks()); } @Test public void testIsAwaitingAcksConfigsReturnNull() { when(requestOptionsMock.getAckTimeout()).thenReturn(null); - Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); + assertFalse("expect false if MessageOptions.ackTimeout null", collector.isAwaitingAcks()); } @Test public void testIsAwaitingAcksConfigsReturnZero() { when(requestOptionsMock.getAckTimeout()).thenReturn(0); - Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); + assertFalse("expect false if MessageOptions.ackTimeout=0", collector.isAwaitingAcks()); } @Test public void testHandleResponse() { - String bodyText = "some body"; + String bodyText = "some body"; Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); @SuppressWarnings("unchecked") Callback onResponse = mock(Callback.class); @@ -147,7 +155,9 @@ public void testHandleResponse() { Callback onRawResponse = mock(Callback.class); when(eventHandlers.onResponse()).thenReturn(onResponse); when(eventHandlers.onRawResponse()).thenReturn(onRawResponse); - Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, + new TypeReference() { + }); // method under test collector.handleMessage(responseMessage); @@ -175,8 +185,10 @@ public void testHandleResponseConversionFailed() { EventHandlers> eventHandlers = mock(EventHandlers.class); when(eventHandlers.onResponse()).thenReturn(onResponse); when(eventHandlers.onRawResponse()).thenReturn(onRawResponse); - TypeReference> payloadTypeReference = new TypeReference>() {}; - Collector> collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, payloadTypeReference); + TypeReference> payloadTypeReference = new TypeReference>() { + }; + Collector> collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, + payloadTypeReference); // make sure that onRawResponse is called even if conversion of payload to custom type fails try { @@ -192,7 +204,7 @@ public void testHandleResponseConversionFailed() { public void testHandleResponseReceivedAck() { Callback onAck = mock(Callback.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.handleMessage(responseMessageWithAck); @@ -206,7 +218,7 @@ public void testHandleResponseReceivedAck() { public void testHandleResponseEndEventNoResponsesRemaining() { Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.handleMessage(responseMessageWithAck); @@ -233,7 +245,7 @@ public void testHandleResponseLastResponse() { Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.handleMessage(responseMessage); RestPayload expectedPayload = new RestPayload.Builder() @@ -263,7 +275,7 @@ public void testHandleResponseWaitForOneMoreResponse() { Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); RestPayload expectedPayload = new RestPayload.Builder() @@ -301,7 +313,7 @@ public void testHandleResponseNoResponsesRemainingButAwaitAck() { Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); //send payload response @@ -329,7 +341,7 @@ public void testHandleResponseReceivedPayloadButAwaitAck() { Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); //send payload response @@ -357,7 +369,7 @@ public void testHandleResponseNoResponsesRemainingAndWaitUntilAckBeforeNow() { Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); //send payload response @@ -380,11 +392,11 @@ public void testHandleResponseReceivedAckWithSameTimeoutValue() { Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); Acknowledge ack = new Acknowledge.Builder().withResponderId(Utils.generateId()).withResponsesRemaining(0).withTimeoutMs(timeoutMs).build(); - Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC, originalMessage.getCorrelationId()); + Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); collector.handleMessage(responseMessageWithAck); @@ -395,7 +407,7 @@ public void testHandleResponseReceivedAckWithSameTimeoutValue() { @Test @SuppressWarnings({ "rawtypes", "unchecked" }) public void testHandleResponseReceivedAckWithUpdatedTimeoutAndNoResponsesRemaining() { - /*ackTimeout = 0, responseTimeout= 50; waitForResponses = 0 + /*ackTimeout = 0, responseTimeout= 100; waitForResponses = 0 */ int timeoutMs = 50; int timeoutMsInAck = 100; @@ -406,44 +418,47 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndNoResponsesRemaini Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); Acknowledge ack = new Acknowledge.Builder().withResponderId(Utils.generateId()).withResponsesRemaining(0).withTimeoutMs(timeoutMsInAck) .build(); - Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC, originalMessage.getCorrelationId()); + Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); collector.handleMessage(responseMessageWithAck); - verify(timeoutManagerMock).enableResponseTimeout(eq(timeoutMsInAck), eq(collector)); + verify(timeoutManagerMock, never()).enableResponseTimeout(eq(timeoutMsInAck), eq(collector)); verify(onEnd).call(any()); } @Test @SuppressWarnings({ "rawtypes", "unchecked" }) public void testHandleResponseReceivedAckWithUpdatedTimeoutAndResponsesRemaining() { - /*ackTimeout = 0, responseTimeout= 50; waitForResponses = 2 + /*ackTimeout = 0, responseTimeout= 100; waitForResponses = 2 */ - int timeoutMs = 50; - int timeoutMsInAck = 100; + int timeoutMs = 100; + int timeoutMsInAck = 200; int responsesRemaining = 2; when(requestOptionsMock.getAckTimeout()).thenReturn(0); when(requestOptionsMock.getResponseTimeout()).thenReturn(timeoutMs); when(requestOptionsMock.getWaitForResponses()).thenReturn(0); + ArgumentCaptor timeoutCaptor = ArgumentCaptor.forClass(Integer.class); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); Acknowledge ack = new Acknowledge.Builder().withResponderId(Utils.generateId()).withResponsesRemaining(responsesRemaining) .withTimeoutMs(timeoutMsInAck).build(); - Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC, originalMessage.getCorrelationId()); + Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); collector.handleMessage(responseMessageWithAck); - verify(timeoutManagerMock).enableResponseTimeout(eq(timeoutMsInAck), eq(collector)); + verify(timeoutManagerMock).enableResponseTimeout(timeoutCaptor.capture(), any()); + + assertThat(timeoutCaptor.getValue()).isBetween(1, timeoutMsInAck); verify(onEnd, never()).call(any()); } @@ -453,9 +468,9 @@ public void testHandleResponseReceivedAcksWithUpdatedTimeoutAndResponsesRemainin /*ackTimeout = 0, responseTimeout= 50; waitForResponses = 2 */ int timeoutMs = 50; - int timeoutMsInAckResponderOne = 100; + int timeoutMsInAckResponderOne = 2000; int responsesRemainingResponderOne = 5; - int timeoutMsInAckResponderTwo = 200; + int timeoutMsInAckResponderTwo = 5000; int responsesRemainingResponderTwo = 7; when(requestOptionsMock.getAckTimeout()).thenReturn(0); when(requestOptionsMock.getResponseTimeout()).thenReturn(timeoutMs); @@ -463,28 +478,31 @@ public void testHandleResponseReceivedAcksWithUpdatedTimeoutAndResponsesRemainin Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); + ArgumentCaptor timeoutCaptor = ArgumentCaptor.forClass(Integer.class); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); Acknowledge ackRespOne = new Acknowledge.Builder().withResponderId(Utils.generateId()).withResponsesRemaining(responsesRemainingResponderOne) .withTimeoutMs(timeoutMsInAckResponderOne).build(); Message messageWithAckOne = TestUtils - .createMsbResponseMessageWithAckNoPayload(ackRespOne, TOPIC, originalMessage.getCorrelationId()); + .createMsbResponseMessageWithAckNoPayload(ackRespOne, TOPIC_RESPONSE, originalMessage.getCorrelationId()); Acknowledge ackRespTwo = new Acknowledge.Builder().withResponderId(Utils.generateId()).withResponsesRemaining(responsesRemainingResponderTwo) .withTimeoutMs(timeoutMsInAckResponderTwo).build(); Message messageWithAckTwo = TestUtils - .createMsbResponseMessageWithAckNoPayload(ackRespTwo, TOPIC, originalMessage.getCorrelationId()); + .createMsbResponseMessageWithAckNoPayload(ackRespTwo, TOPIC_RESPONSE, originalMessage.getCorrelationId()); collector.handleMessage(messageWithAckOne); - verify(timeoutManagerMock).enableResponseTimeout(eq(timeoutMsInAckResponderOne), eq(collector)); + verify(timeoutManagerMock).enableResponseTimeout(timeoutCaptor.capture(), any()); assertEquals(responsesRemainingResponderOne, collector.getResponsesRemaining()); + assertThat(timeoutCaptor.getValue()).isBetween(1, timeoutMsInAckResponderOne - 1); verify(onEnd, never()).call(any()); collector.handleMessage(messageWithAckTwo); - verify(timeoutManagerMock, times(1)).enableResponseTimeout(eq(timeoutMsInAckResponderTwo), eq(collector)); + verify(timeoutManagerMock, times(2)).enableResponseTimeout(timeoutCaptor.capture(), any()); assertEquals(responsesRemainingResponderOne + responsesRemainingResponderTwo, collector.getResponsesRemaining()); + assertThat(timeoutCaptor.getValue()).isBetween(1, timeoutMsInAckResponderTwo - 1); verify(onEnd, never()).call(any()); } @@ -507,7 +525,7 @@ public void testHandleResponseEnsureResponsesRemainingIsDecreased() { Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); - Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); assertEquals(responsesRemaining, collector.getResponsesRemaining()); @@ -523,11 +541,232 @@ public void testHandleResponseEnsureResponsesRemainingIsDecreased() { verify(onEnd).call(any()); } + @Test + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void testHandleResponseReceivedAckWithUpdatedTimeoutAndOneResponseRemaining() throws InterruptedException, IOException { + int timeoutMs = 200; + int timeoutMsInAck = 5000; + String responderId = "b"; + + when(requestOptionsMock.getAckTimeout()).thenReturn(0); + when(requestOptionsMock.getResponseTimeout()).thenReturn(timeoutMs); + when(requestOptionsMock.getWaitForResponses()).thenReturn(0); + + this.msbContext = TestUtils.createMsbContextBuilder() + .withMsbConfigurations(msbConfigurationsMock) + .withMessageFactory(messageFactoryMock) + .withChannelManager(channelManagerMock) + .withClock(Clock.systemDefaultZone()) + .withTimeoutManager(new TimeoutManager(1)) + .withCollectorManagerFactory(collectorManagerFactoryMock) + .build(); + + Callback onEnd = mock(Callback.class); + when(eventHandlers.onEnd()).thenReturn(onEnd); + + Collector collector = createCollector(); + collector.listenForResponses(); + + Acknowledge ack = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(1).withTimeoutMs(timeoutMsInAck) + .build(); + Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); + collector.handleMessage(responseMessageWithAck); + + //simulate responder response + Acknowledge responseAck = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(-1).build(); + ObjectMapper payloadMapper = TestUtils.createMessageMapper(); + JsonNode payloadNode = payloadMapper.readValue(String.format("{\"body\": \"%s\" }", "test response payload body"), JsonNode.class); + + Message responderMessage = TestUtils.createMsbResponseMessage(responseAck, payloadNode, TOPIC_RESPONSE, "someCorrelationId"); + + //send message + collector.handleMessage(responderMessage); + + verify(onEnd, timeout(1500)).call(any()); + } + + @Test + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void testHandleResponseReceivedAckWithUpdatedTimeoutAndTwoResponsesRemaining() throws InterruptedException, IOException { + int timeoutMs = 200; + int timeoutMsInAck = 5000; + String responderId = "b"; + + when(requestOptionsMock.getAckTimeout()).thenReturn(0); + when(requestOptionsMock.getResponseTimeout()).thenReturn(timeoutMs); + when(requestOptionsMock.getWaitForResponses()).thenReturn(0); + + this.msbContext = TestUtils.createMsbContextBuilder() + .withMsbConfigurations(msbConfigurationsMock) + .withMessageFactory(messageFactoryMock) + .withChannelManager(channelManagerMock) + .withClock(Clock.systemDefaultZone()) + .withTimeoutManager(new TimeoutManager(1)) + .withCollectorManagerFactory(collectorManagerFactoryMock) + .build(); + + Callback onEnd = mock(Callback.class); + when(eventHandlers.onEnd()).thenReturn(onEnd); + + Collector collector = createCollector(); + collector.listenForResponses(); + + Acknowledge ack = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(2).withTimeoutMs(timeoutMsInAck) + .build(); + Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); + collector.handleMessage(responseMessageWithAck); + + //simulate responder response + Acknowledge responseAck = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(-1).build(); + ObjectMapper payloadMapper = TestUtils.createMessageMapper(); + JsonNode payloadNode = payloadMapper.readValue(String.format("{\"body\": \"%s\" }", "test response payload body"), JsonNode.class); + + Message responderMessage = TestUtils.createMsbResponseMessage(responseAck, payloadNode, TOPIC_RESPONSE, "someCorrelationId"); + + //send first message + collector.handleMessage(responderMessage); + + //send second message after initial response + Thread.sleep(200); + collector.handleMessage(responderMessage); + + verify(onEnd, timeout(1500)).call(any()); + } + + @Test + public void testEndUnregisterCollector() { + Collector collector = createCollector(); + + collector.end(); + + verify(collectorManagerMock).unregisterCollector(collector); + } + + @Test + public void testEndHandlerEndCalled() { + Callback onEnd = mock(Callback.class); + when(eventHandlers.onEnd()).thenReturn(onEnd); + Collector collector = createCollector(); + + collector.end(); + + verify(onEnd).call(any()); + } + + @Test + public void testProcessAckNull() { + Collector collector = createCollector(); + + collector.processAck(null); + + verify(timeoutManagerMock, never()).enableResponseTimeout(anyInt(), any()); + } + + @Test + public void testProcessAckPerResponder() { + int timeoutMs = 5000; + ArgumentCaptor timeoutCaptor = ArgumentCaptor.forClass(Integer.class); + Collector collector = createCollector(); + + collector.processAck(new Acknowledge.Builder().withResponderId("a").withTimeoutMs(timeoutMs).build()); + + verify(timeoutManagerMock).enableResponseTimeout(timeoutCaptor.capture(), any()); + assertThat(timeoutCaptor.getValue()).isBetween(1, timeoutMs); + } + + @Test + public void testProcessAckWillTakeDefaultTimeoutIsMaxAndNotCallTimerAgain() { + int timeoutMs = 1500; + //will set default 3000; + when(requestOptionsMock.getResponseTimeout()).thenReturn(null); + Collector collector = createCollector(); + + collector.processAck(new Acknowledge.Builder().withResponderId("a").withTimeoutMs(timeoutMs).build()); + + verify(timeoutManagerMock, never()).enableResponseTimeout(anyInt(), any()); + } + + @Test + public void testEndHandlerTimersStopped() { + ScheduledFuture ackTimerMock = mock(ScheduledFuture.class); + ScheduledFuture timeoutTimerMock = mock(ScheduledFuture.class); + when(timeoutManagerMock.enableAckTimeout(anyInt(), any())).thenReturn(ackTimerMock); + when(timeoutManagerMock.enableResponseTimeout(anyInt(), any())).thenReturn(timeoutTimerMock); + when(requestOptionsMock.getAckTimeout()).thenReturn(100); + when(requestOptionsMock.getResponseTimeout()).thenReturn(100); + + Collector collector = createCollector(); + collector.listenForResponses(); + collector.waitForAcks(); + collector.waitForResponses(); + + verify(timeoutManagerMock).enableAckTimeout(anyInt(), any()); + verify(timeoutManagerMock).enableResponseTimeout(anyInt(), any()); + + collector.end(); + + verify(ackTimerMock).cancel(anyBoolean()); + verify(timeoutTimerMock).cancel(anyBoolean()); + } + @Test public void testListenForResponses() { - Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() {}); + Collector collector = createCollector(); collector.listenForResponses(); verify(collectorManagerMock).registerCollector(collector); } + + @Test + public void testWaitForResponses() throws InterruptedException { + int timeoutMs = 1000; + int initCollectorAfter = 50; + ArgumentCaptor timeoutCaptor = ArgumentCaptor.forClass(Integer.class); + when(requestOptionsMock.getResponseTimeout()).thenReturn(timeoutMs); + Collector collector = createCollector(); + Thread.sleep(initCollectorAfter); + collector.waitForResponses(); + + verify(timeoutManagerMock).enableResponseTimeout(timeoutCaptor.capture(), any()); + int timeoutLeftToWait = timeoutMs - initCollectorAfter; + assertThat(timeoutCaptor.getValue()).isBetween(1, timeoutLeftToWait); + } + + @Test + public void testWaitForResponsesReceivedGreaterTimeout() throws InterruptedException { + int timeoutMs = 1000; + int updatedTimeoutMs = 2000; + int receivedAckAfter = 50; + ArgumentCaptor timeoutCaptor = ArgumentCaptor.forClass(Integer.class); + when(requestOptionsMock.getResponseTimeout()).thenReturn(timeoutMs); + Collector collector = createCollector(); + Thread.sleep(receivedAckAfter); + collector.processAck(new Acknowledge.Builder().withResponderId("a").withTimeoutMs(updatedTimeoutMs).build()); + collector.waitForResponses(); + + verify(timeoutManagerMock, times(2)).enableResponseTimeout(timeoutCaptor.capture(), any()); + int timeoutLeftToWait = updatedTimeoutMs - receivedAckAfter; + assertThat(timeoutCaptor.getValue()).isBetween(1, timeoutLeftToWait); + } + + @Test + public void testWaitForAcks() { + int timeoutMs = 1000; + ArgumentCaptor timeoutCaptor = ArgumentCaptor.forClass(Integer.class); + when(requestOptionsMock.getAckTimeout()).thenReturn(timeoutMs); + Collector collector = createCollector(); + + //set waitForAcksUntil value + collector.listenForResponses(); + collector.waitForAcks(); + + verify(timeoutManagerMock).enableAckTimeout(timeoutCaptor.capture(), any()); + assertThat(timeoutCaptor.getValue()).isBetween(500, timeoutMs); + } + + private Collector createCollector() { + return new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() { + }); + } + } \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index b14dae7a..4fdacb9f 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -84,7 +84,6 @@ public void testPublishWaitForResponsesAck() throws Exception { Message responseMessage = TestUtils.createMsbRequestMessage("some:topic", "body text"); collectorMock.handleMessage(responseMessage); - } @Test @@ -222,8 +221,12 @@ private RequesterImpl initRequesterForResponsesWith(Integer numberO MessageTemplate messageTemplateMock = mock(MessageTemplate.class); - RequestOptions requestOptionsMock = new RequestOptions.Builder().withMessageTemplate(messageTemplateMock).withWaitForResponses(numberOfResponses) - .withResponseTimeout(respTimeout).withAckTimeout(ackTimeout).build(); + RequestOptions requestOptionsMock = new RequestOptions.Builder() + .withMessageTemplate(messageTemplateMock) + .withWaitForResponses(numberOfResponses) + .withResponseTimeout(respTimeout) + .withAckTimeout(ackTimeout) + .build(); when(channelManagerMock.findOrCreateProducer(anyString())).thenReturn(producerMock); diff --git a/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java b/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java index 85d69a4b..930a8ac8 100644 --- a/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java +++ b/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java @@ -189,6 +189,22 @@ public static Message createMsbResponseMessageWithAckNoPayload(Acknowledge ack, .build(); } + public static Message createMsbResponseMessage(Acknowledge ack, JsonNode payload, String topicTo, String correlationId) { + MsbConfig msbConf = createMsbConfigurations(); + Clock clock = Clock.systemDefaultZone(); + + Topics topic = new Topics(topicTo, null); + MetaMessage.Builder metaBuilder = createSimpleMetaBuilder(msbConf, clock); + return new Message.Builder() + .withCorrelationId(Utils.ifNull(correlationId, Utils.generateId())) + .withId(Utils.generateId()) + .withTopics(topic) + .withMetaBuilder(metaBuilder) + .withPayload(payload) + .withAck(ack) + .build(); + } + public static Message.Builder createMessageBuilder(Clock clock) { MsbConfig msbConf = createMsbConfigurations(); diff --git a/pom.xml b/pom.xml index 671b4d47..b77b5f98 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,11 @@ junit 4.12 + + org.assertj + assertj-core + 3.2.0 + org.jbehave jbehave-core @@ -137,6 +142,11 @@ junit-toolbox test + + org.assertj + assertj-core + test + org.slf4j slf4j-api From 906e1b2eeefc97436009fe7d3dd5bc0aee934f70 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Wed, 2 Dec 2015 23:17:36 +0200 Subject: [PATCH 003/226] Added prefetchCount --- .../adapters/amqp/AmqpConsumerAdapter.java | 8 ++- .../msb/config/amqp/AmqpBrokerConfig.java | 26 ++++++--- amqp/src/main/resources/amqp.conf | 3 ++ .../adapters/amqp/AmqpAdapterFactoryTest.java | 14 ++--- .../amqp/AmqpConsumerAdapterTest.java | 31 +++++------ .../msb/config/amqp/AmqpBrokerConfigTest.java | 54 ++++++++++++++++--- 6 files changed, 97 insertions(+), 39 deletions(-) diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java index d931ab31..a47eed2b 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java @@ -1,15 +1,17 @@ package io.github.tcdl.msb.adapters.amqp; -import com.rabbitmq.client.Channel; import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.exception.ChannelException; import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; import io.github.tcdl.msb.support.Utils; -import org.apache.commons.lang3.Validate; import java.io.IOException; import java.util.concurrent.ExecutorService; +import org.apache.commons.lang3.Validate; + +import com.rabbitmq.client.Channel; + public class AmqpConsumerAdapter implements ConsumerAdapter { private String topic; @@ -50,11 +52,13 @@ public AmqpConsumerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, Amqp public void subscribe(RawMessageHandler msgHandler) { String groupId = adapterConfig.getGroupId().orElse(Utils.generateId()); boolean durable = adapterConfig.isDurable(); + int prefetchCount = adapterConfig.getPrefetchCount(); String queueName = generateQueueName(topic, groupId, durable); try { channel.queueDeclare(queueName, durable /* durable */, false /* exclusive */, !durable /*auto-delete */, null); + channel.basicQos(prefetchCount); // Don't accept more messages if we have any unacknowledged channel.queueBind(queueName, exchangeName, ""); consumerTag = channel.basicConsume(queueName, false /* autoAck */, new AmqpMessageConsumer(channel, consumerThreadPool, msgHandler, adapterConfig)); diff --git a/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java b/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java index 162e6ab8..905db5e3 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java +++ b/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java @@ -1,11 +1,11 @@ package io.github.tcdl.msb.config.amqp; +import io.github.tcdl.msb.api.exception.ConfigurationException; +import io.github.tcdl.msb.config.ConfigurationUtil; + import java.nio.charset.Charset; import java.util.Optional; -import io.github.tcdl.msb.config.ConfigurationUtil; -import io.github.tcdl.msb.api.exception.ConfigurationException; - import com.typesafe.config.Config; public class AmqpBrokerConfig { @@ -26,11 +26,12 @@ public class AmqpBrokerConfig { private final boolean requeueRejectedMessages; private final int heartbeatIntervalSec; private final long networkRecoveryIntervalMs; + private final int prefetchCount; public AmqpBrokerConfig(Charset charset, String host, int port, Optional username, Optional password, Optional virtualHost, boolean useSSL, Optional groupId, boolean durable, int consumerThreadPoolSize, int consumerThreadPoolQueueCapacity, boolean requeueRejectedMessages, - int heartbeatIntervalSec, long networkRecoveryIntervalMs) { + int heartbeatIntervalSec, long networkRecoveryIntervalMs, int prefetchCount) { this.charset = charset; this.port = port; this.host = host; @@ -45,6 +46,7 @@ public AmqpBrokerConfig(Charset charset, String host, int port, this.requeueRejectedMessages = requeueRejectedMessages; this.heartbeatIntervalSec = heartbeatIntervalSec; this.networkRecoveryIntervalMs = networkRecoveryIntervalMs; + this.prefetchCount = prefetchCount; } public static class AmqpBrokerConfigBuilder { @@ -62,6 +64,7 @@ public static class AmqpBrokerConfigBuilder { private boolean requeueRejectedMessages; private int heartbeatIntervalSec; private long networkRecoveryIntervalMs; + private int prefetchCount; /** * Initialize Builder with Config @@ -91,6 +94,7 @@ public AmqpBrokerConfigBuilder withConfig(Config config) { this.requeueRejectedMessages = ConfigurationUtil.getBoolean(config, "requeueRejectedMessages"); this.heartbeatIntervalSec = ConfigurationUtil.getInt(config, "heartbeatIntervalSec"); this.networkRecoveryIntervalMs = ConfigurationUtil.getLong(config, "networkRecoveryIntervalMs"); + this.prefetchCount = ConfigurationUtil.getInt(config, "prefetchCount"); return this; } @@ -99,7 +103,8 @@ public AmqpBrokerConfigBuilder withConfig(Config config) { */ public AmqpBrokerConfig build() { return new AmqpBrokerConfig(charset, host, port, username, password, virtualHost, useSSL, - groupId, durable, consumerThreadPoolSize, consumerThreadPoolQueueCapacity, requeueRejectedMessages, heartbeatIntervalSec, networkRecoveryIntervalMs); + groupId, durable, consumerThreadPoolSize, consumerThreadPoolQueueCapacity, + requeueRejectedMessages, heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); } } @@ -163,12 +168,17 @@ public long getNetworkRecoveryIntervalMs() { return networkRecoveryIntervalMs; } + public int getPrefetchCount() { + return prefetchCount; + } + @Override public String toString() { return String.format("AmqpBrokerConfig [charset=%s, host=%s, port=%d, username=%s, password=xxx, virtualHost=%s, useSSL=%s, groupId=%s, durable=%s, " - + "consumerThreadPoolSize=%s, consumerThreadPoolQueueCapacity=%s, requeueRejectedMessages=%s, heartbeatIntervalSec=%s, networkRecoveryIntervalMs=%s]", - charset, host, port, username, virtualHost, useSSL, groupId, durable, consumerThreadPoolSize, consumerThreadPoolQueueCapacity, requeueRejectedMessages, - heartbeatIntervalSec, networkRecoveryIntervalMs); + + "consumerThreadPoolSize=%s, consumerThreadPoolQueueCapacity=%s, requeueRejectedMessages=%s, heartbeatIntervalSec=%s, " + + "networkRecoveryIntervalMs=%s, prefetchCount=%s]", + charset, host, port, username, virtualHost, useSSL, groupId, durable, consumerThreadPoolSize, consumerThreadPoolQueueCapacity, + requeueRejectedMessages, heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); } } \ No newline at end of file diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf index 2198035e..d4fc254a 100644 --- a/amqp/src/main/resources/amqp.conf +++ b/amqp/src/main/resources/amqp.conf @@ -24,4 +24,7 @@ config.amqp = { heartbeatIntervalSec = 1 # Interval of connection recovery attempts. See for more details: https://www.rabbitmq.com/api-guide.html#connection-recovery networkRecoveryIntervalMs = 5000 + + # Specify the size of the limit of unacknowledged messages on a queue basis + prefetchCount = 1 } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java index 4d8ff3aa..3d79808e 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java @@ -2,12 +2,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; - -import com.rabbitmq.client.Recoverable; +import static org.mockito.Mockito.withSettings; import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.adapters.ProducerAdapter; import io.github.tcdl.msb.config.MsbConfig; @@ -24,13 +25,10 @@ import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.Recoverable; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.withSettings; - public class AmqpAdapterFactoryTest { final Charset charset = Charset.forName("UTF-8"); final String host = "127.0.0.1"; @@ -46,6 +44,7 @@ public class AmqpAdapterFactoryTest { final boolean requeueRejectedMessages = true; final int heartbeatIntervalSec = 1; final long networkRecoveryIntervalMs = 5000; + final int prefetchCount = 1; AmqpBrokerConfig amqpConfig; AmqpAdapterFactory amqpAdapterFactory; @@ -84,7 +83,8 @@ public void setUp() { amqpConfig = new AmqpBrokerConfig(charset, host, port, Optional.of(username), Optional.of(password), Optional.of(virtualHost), useSSL, Optional.of(groupId), durable, - consumerThreadPoolSize, consumerThreadPoolQueueCapacity, requeueRejectedMessages, heartbeatIntervalSec, networkRecoveryIntervalMs); + consumerThreadPoolSize, consumerThreadPoolQueueCapacity, requeueRejectedMessages, + heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); amqpAdapterFactory = new AmqpAdapterFactory() { @Override diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java index 7d0fd43a..61cda8f4 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java @@ -1,19 +1,5 @@ package io.github.tcdl.msb.adapters.amqp; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Consumer; -import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.Optional; -import java.util.concurrent.ExecutorService; - import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; @@ -22,6 +8,21 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Optional; +import java.util.concurrent.ExecutorService; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.Consumer; public class AmqpConsumerAdapterTest { @@ -125,7 +126,7 @@ public void testUnsubscribe() throws IOException { private AmqpConsumerAdapter createAdapter(String topic, String groupId, boolean durable) { AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(), - false, Optional.of(groupId), durable, 5, 20, true, 1, 5000); + false, Optional.of(groupId), durable, 5, 20, true, 1, 5000, 1); return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, mockConsumerThreadPool); } } \ No newline at end of file diff --git a/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java b/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java index 4b4caf00..5807f6a3 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java @@ -1,16 +1,17 @@ package io.github.tcdl.msb.config.amqp; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import io.github.tcdl.msb.api.exception.ConfigurationException; -import org.junit.Test; - -import java.nio.charset.Charset; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import io.github.tcdl.msb.api.exception.ConfigurationException; + +import java.nio.charset.Charset; + +import org.junit.Test; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; public class AmqpBrokerConfigTest { @@ -28,6 +29,7 @@ public class AmqpBrokerConfigTest { final boolean requeueRejectedMessages = true; final int heartbeatIntervalSec = 1; final long networkRecoveryIntervalMs = 5000; + final int prefetchCount = 1; @Test public void testBuildAmqpBrokerConfig() { @@ -46,6 +48,7 @@ public void testBuildAmqpBrokerConfig() { + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; Config amqpConfig = ConfigFactory.parseString(configStr).getConfig("config.amqp"); @@ -68,6 +71,9 @@ public void testBuildAmqpBrokerConfig() { assertEquals(heartbeatIntervalSec, brokerConfig.getHeartbeatIntervalSec()); assertEquals(networkRecoveryIntervalMs, brokerConfig.getNetworkRecoveryIntervalMs()); + + assertEquals(prefetchCount, brokerConfig.getPrefetchCount()); + } @Test @@ -83,6 +89,7 @@ public void testOptionalConfigurationOptions() { + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; Config amqpConfig = ConfigFactory.parseString(configStr).getConfig("config.amqp"); @@ -113,6 +120,7 @@ public void testHostConfigurationOption() { + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; testMandatoryConfigurationOption(configStr, "host"); @@ -134,6 +142,7 @@ public void testPortConfigurationOption() { + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; testMandatoryConfigurationOption(configStr, "port"); @@ -155,6 +164,7 @@ public void testDurableConfigurationOption() { + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; testMandatoryConfigurationOption(configStr, "durable"); @@ -176,6 +186,7 @@ public void testConsumerThreadPoolSizeConfigurationOption() { + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; testMandatoryConfigurationOption(configStr, "consumerThreadPoolSize"); @@ -197,6 +208,7 @@ public void testConsumerThreadPoolQueueCapacityConfigurationOption() { + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; testMandatoryConfigurationOption(configStr, "consumerThreadPoolQueueCapacity"); @@ -218,6 +230,7 @@ public void testCharsetConfigurationOption() { + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; testMandatoryConfigurationOption(configStr, "charsetName"); @@ -241,6 +254,7 @@ public void testInvalidCharset() { + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; AmqpBrokerConfig.AmqpBrokerConfigBuilder builder = createConfigBuilder(configStr); @@ -262,6 +276,7 @@ public void testUseSSLConfigurationOption() { + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; testMandatoryConfigurationOption(configStr, "useSSL"); @@ -283,6 +298,7 @@ public void testRequeueRejectedMessagesOption() { + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; testMandatoryConfigurationOption(configStr, "requeueRejectedMessages"); @@ -304,6 +320,7 @@ public void testHeartbeatIntervalOption() { + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; testMandatoryConfigurationOption(configStr, "heartbeatIntervalSec"); @@ -325,11 +342,34 @@ public void testNetworkRecoveryIntervalOption() { + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + + " prefetchCount = " + prefetchCount + "\n" + "}"; testMandatoryConfigurationOption(configStr, "networkRecoveryIntervalMs"); } + @Test + public void testPrefetchCountOption() { + String configStr = "config.amqp {" + + " charsetName = \"" + charsetName + "\"\n" + + " host = \"" + host + "\"\n" + + " port = \"" + port + "\"\n" + + " username = \"" + username + "\"\n" + + " password = \"" + password + "\"\n" + + " virtualHost = \"" + virtualHost + "\"\n" + + " useSSL = \"" + useSSL + "\"\n" + + " groupId = \"" + groupId + "\"\n" + + " durable = " + durable + "\n" + + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" + + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + + "}"; + + testMandatoryConfigurationOption(configStr, "prefetchCount"); + } + private void testMandatoryConfigurationOption(String configStr, String path) { try { AmqpBrokerConfig.AmqpBrokerConfigBuilder builder = createConfigBuilder(configStr); From f8c32d76fbb206e4a0e0425dd30cef73f2a5be49 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Fri, 4 Dec 2015 06:57:13 +0200 Subject: [PATCH 004/226] Implemented explicit message confirms/rejects --- .../tcdl/msb/acceptance/MsbTestHelper.java | 9 +- .../msb/acceptance/MultipleRequester.java | 7 +- .../amqp/AmqpAcknowledgementHandler.java | 81 ++++++++++++ .../adapters/amqp/AmqpMessageConsumer.java | 33 +++-- .../amqp/AmqpMessageProcessingTask.java | 22 ++-- .../amqp/AmqpConsumerAdapterTest.java | 8 +- .../amqp/AmqpMessageConsumerTest.java | 36 +++--- .../amqp/AmqpMessageProcessingTaskTest.java | 26 ++-- .../tcdl/msb/cli/CliMessageHandler.java | 14 +- .../tcdl/msb/cli/CliMessageHandlerTest.java | 16 +-- .../java/io/github/tcdl/msb/Consumer.java | 16 ++- .../io/github/tcdl/msb/MessageHandler.java | 4 +- .../tcdl/msb/adapters/ConsumerAdapter.java | 19 ++- .../tcdl/msb/adapters/mock/MockAdapter.java | 12 +- .../io/github/tcdl/msb/api/Requester.java | 9 +- .../io/github/tcdl/msb/api/Responder.java | 3 +- .../github/tcdl/msb/collector/Collector.java | 36 +++--- .../tcdl/msb/collector/CollectorManager.java | 10 +- .../github/tcdl/msb/events/EventHandlers.java | 23 ++-- .../tcdl/msb/impl/NoopResponderImpl.java | 17 ++- .../github/tcdl/msb/impl/RequesterImpl.java | 13 +- .../github/tcdl/msb/impl/ResponderImpl.java | 21 ++- .../tcdl/msb/impl/ResponderServerImpl.java | 17 ++- .../agent/DefaultChannelMonitorAgent.java | 4 +- .../DefaultChannelMonitorAggregator.java | 23 ++-- .../msb/monitor/aggregator/HeartbeatTask.java | 7 +- .../msb/ChannelManagerConcurrentTest.java | 16 ++- .../github/tcdl/msb/ChannelManagerTest.java | 27 ++-- .../java/io/github/tcdl/msb/ConsumerTest.java | 63 +++++---- .../msb/adapters/mock/MockAdapterTest.java | 17 ++- .../github/tcdl/msb/api/ChannelMonitorIT.java | 19 +-- .../tcdl/msb/api/RequesterResponderIT.java | 21 +-- .../io/github/tcdl/msb/api/ResponderIT.java | 25 ++-- .../msb/collector/CollectorManagerTest.java | 28 ++-- .../tcdl/msb/collector/CollectorTest.java | 122 ++++++++++-------- .../tcdl/msb/impl/RequesterImplTest.java | 45 +++---- .../tcdl/msb/impl/ResponderImplTest.java | 34 ++--- .../msb/impl/ResponderServerImplTest.java | 35 ++--- .../DefaultChannelMonitorAggregatorTest.java | 29 +++-- .../monitor/aggregator/HeartbeatTaskTest.java | 29 +++-- .../tcdl/msb/examples/FacetsAggregator.java | 5 +- .../github/tcdl/msb/examples/PingService.java | 7 +- 42 files changed, 616 insertions(+), 392 deletions(-) create mode 100644 amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java index 54e83972..3e373573 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java @@ -1,7 +1,5 @@ package io.github.tcdl.msb.acceptance; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.typesafe.config.Config; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.MsbContext; @@ -17,6 +15,9 @@ import java.util.HashMap; import java.util.Map; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.typesafe.config.Config; + /** * Utility to simplify using requester and responder server */ @@ -97,13 +98,13 @@ public void sendRequest(Requester requester, Object payload, Integer wait public void sendRequest(Requester requester, Object payload, boolean waitForAck, Integer waitForResponses, Callback ackCallback, Callback responseCallback) throws Exception { - requester.onAcknowledge(acknowledge -> { + requester.onAcknowledge((acknowledge, ackHandler) -> { System.out.println(">>> ACKNOWLEDGE: " + acknowledge); if (waitForAck && ackCallback != null) ackCallback.call(acknowledge); }); - requester.onResponse(response -> { + requester.onResponse((response, ackHandler) -> { System.out.println(">>> RESPONSE: " + response); if (waitForResponses != null && waitForResponses > 0 && responseCallback != null) { responseCallback.call(response); diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequester.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequester.java index 91446e7a..43377fc7 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequester.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequester.java @@ -1,6 +1,5 @@ package io.github.tcdl.msb.acceptance; -import com.fasterxml.jackson.core.type.TypeReference; import io.github.tcdl.msb.api.MsbContext; import io.github.tcdl.msb.api.MsbContextBuilder; import io.github.tcdl.msb.api.RequestOptions; @@ -13,6 +12,8 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import com.fasterxml.jackson.core.type.TypeReference; + public class MultipleRequester { public static void main(String... args) throws Exception { @@ -54,10 +55,10 @@ public static void runRequester(String namespace, String requestId, String query CompletableFuture.supplyAsync(() -> { msbContext.getObjectFactory().createRequester(namespace, options, new TypeReference>() {}) - .onAcknowledge(acknowledge -> + .onAcknowledge((acknowledge, ackHandler) -> System.out.println(">>> ACK timeout: " + acknowledge.getTimeoutMs()) ) - .onResponse(payload -> { + .onResponse((payload, ackHandler) -> { System.out.println(">>> RESPONSE body: " + payload.getBody()); callback.accept(payload.getBody()); }) diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java new file mode 100644 index 00000000..5368d40e --- /dev/null +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java @@ -0,0 +1,81 @@ +package io.github.tcdl.msb.adapters.amqp; + +import io.github.tcdl.msb.adapters.ConsumerAdapter.AcknowledgementHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.rabbitmq.client.Channel; + +/** + * {@link AmqpAcknowledgementHandler} provides acknowledgement for AMQP broker. + * Used from {@link AmqpMessageConsumer}. + */ + +public class AmqpAcknowledgementHandler implements AcknowledgementHandler { + + private static final String ACK_WAS_ALREADY_SENT = "Acknowledgement was already sent during message processing."; + + private static final Logger LOG = LoggerFactory.getLogger(AmqpAcknowledgementHandler.class); + + final Channel channel; + final String consumerTag; + final String bodyStr; + final long deliveryTag; + final boolean isRequeueRejectedMessages; + + boolean isAcknowledgementSent = false; + + public AmqpAcknowledgementHandler(Channel channel, String consumerTag, String bodyStr, long deliveryTag, + boolean isRequeueRejectedMessages) { + super(); + this.channel = channel; + this.consumerTag = consumerTag; + this.bodyStr = bodyStr; + this.deliveryTag = deliveryTag; + this.isRequeueRejectedMessages = isRequeueRejectedMessages; + } + + @Override + public void confirmMessage() { + if (!isAcknowledgementSent) { + try { + channel.basicAck(deliveryTag, false); + LOG.debug(String.format("[consumer tag: %s] AMQP ack has been sent for message '%s'", consumerTag, bodyStr)); + isAcknowledgementSent = true; + } catch (Exception e) { + LOG.error(String.format("[consumer tag: %s] Got exception:", consumerTag), e); + } + } else { + LOG.warn(ACK_WAS_ALREADY_SENT); + } + } + + @Override + public void rejectMessage() { + if (!isAcknowledgementSent) { + try { + channel.basicReject(deliveryTag, isRequeueRejectedMessages); + LOG.error(String.format("[consumer tag: %s] AMQP reject has been sent for message: %s", consumerTag, bodyStr)); + isAcknowledgementSent = true; + } catch (Exception e) { + LOG.error(String.format("[consumer tag: %s] Got exception:", consumerTag), e); + } + } else { + LOG.warn(ACK_WAS_ALREADY_SENT); + } + } + + public void autoConfirm() { + if (!isAcknowledgementSent) { + confirmMessage(); + } + } + + public void autoReject() { + if (!isAcknowledgementSent) { + rejectMessage(); + } + } + +} diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java index 41e3c93c..f9a4c34a 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java @@ -1,18 +1,20 @@ package io.github.tcdl.msb.adapters.amqp; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.Envelope; import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.ExecutorService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; + /** * Special consumer that allows to process messages coming from single AMQP channel in parallel. * @@ -45,23 +47,30 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp Charset charset = amqpBrokerConfig.getCharset(); boolean requeueRejectedMessages = amqpBrokerConfig.isRequeueRejectedMessages(); String bodyStr = new String(body, charset); + long deliveryTag = envelope.getDeliveryTag(); + LOG.debug(String.format("[consumer tag: %s] Message consumed from broker: %s", consumerTag, bodyStr)); + + AmqpAcknowledgementHandler ackHandler = createAcknowledgementHandler(getChannel(), + consumerTag, bodyStr, deliveryTag, requeueRejectedMessages); try { - consumerThreadPool.submit(new AmqpMessageProcessingTask(consumerTag, bodyStr, getChannel(), envelope.getDeliveryTag(), msgHandler)); + consumerThreadPool.submit(new AmqpMessageProcessingTask(consumerTag, bodyStr, msgHandler, ackHandler)); LOG.debug(String.format("[consumer tag: %s] Message has been put in the processing queue: %s. About to send AMQP ack...", consumerTag, bodyStr)); - - getChannel().basicAck(envelope.getDeliveryTag(), false); - LOG.debug(String.format("[consumer tag: %s] AMQP ack has been sent for message '%s'", consumerTag, bodyStr)); } catch (Exception e) { LOG.error(String.format("[consumer tag: %s] Couldn't put message in the processing queue: %s. About to send AMQP reject...", consumerTag, bodyStr), e); - getChannel().basicReject(envelope.getDeliveryTag(), requeueRejectedMessages); - LOG.error(String.format("[consumer tag: %s] AMQP reject has been sent for message: %s", consumerTag, bodyStr)); + ackHandler.autoReject(); } } catch (Exception e) { // Catch all exceptions to prevent AMQP channel to be closed LOG.error(String.format("[consumer tag: %s] Got exception while processing incoming message", consumerTag), e); } } + + AmqpAcknowledgementHandler createAcknowledgementHandler(Channel channel, String consumerTag, + String bodyStr, long deliveryTag, boolean isRequeueRejectedMessages) { + return new AmqpAcknowledgementHandler(channel, consumerTag, bodyStr, deliveryTag, isRequeueRejectedMessages); + } + } diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java index 9db2df50..d2e42eef 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java @@ -1,7 +1,7 @@ package io.github.tcdl.msb.adapters.amqp; -import com.rabbitmq.client.Channel; import io.github.tcdl.msb.adapters.ConsumerAdapter; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,18 +11,17 @@ public class AmqpMessageProcessingTask implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageProcessingTask.class); - String consumerTag; - String body; - Channel channel; - long deliveryTag; - ConsumerAdapter.RawMessageHandler msgHandler; + final String consumerTag; + final String body; + final ConsumerAdapter.RawMessageHandler msgHandler; + final AmqpAcknowledgementHandler ackHandler; - public AmqpMessageProcessingTask(String consumerTag, String body, Channel channel, long deliveryTag, ConsumerAdapter.RawMessageHandler msgHandler) { + public AmqpMessageProcessingTask(String consumerTag, String body, ConsumerAdapter.RawMessageHandler msgHandler, + AmqpAcknowledgementHandler ackHandler) { this.consumerTag = consumerTag; this.body = body; - this.channel = channel; - this.deliveryTag = deliveryTag; this.msgHandler = msgHandler; + this.ackHandler = ackHandler; } /** @@ -34,10 +33,13 @@ public AmqpMessageProcessingTask(String consumerTag, String body, Channel channe public void run() { try { LOG.debug(String.format("[consumer tag: %s] Starting message processing: %s", consumerTag, body)); - msgHandler.onMessage(body); + msgHandler.onMessage(body, ackHandler); LOG.debug(String.format("[consumer tag: %s] Message has been processed: %s", consumerTag, body)); + ackHandler.autoConfirm(); } catch (Exception e) { LOG.error(String.format("[consumer tag: %s] Failed to process message %s", consumerTag, body), e); + ackHandler.autoReject(); } } + } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java index 61cda8f4..818e965a 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java @@ -47,7 +47,7 @@ public void testTopicExchangeCreated() throws Exception { String topicName = "myTopic"; AmqpConsumerAdapter adapter = createAdapter(topicName, "myGroupId", false); - adapter.subscribe(jsonMessage -> { + adapter.subscribe((jsonMessage, ackHandler) -> { }); verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null); @@ -64,7 +64,7 @@ public void testInitializationError() throws IOException { public void testSubscribeTransientQueueCreated() throws IOException { AmqpConsumerAdapter adapter = createAdapter("myTopic", "myGroupId", false); - adapter.subscribe(jsonMessage -> { + adapter.subscribe((jsonMessage, ackHandler) -> { }); // Verify that the queue has been declared with correct name and settings @@ -81,7 +81,7 @@ public void testSubscribeTransientQueueCreated() throws IOException { public void testSubscribeDurableQueueCreated() throws IOException { AmqpConsumerAdapter adapter = createAdapter("myTopic", "myGroupId", true); - adapter.subscribe(jsonMessage -> { + adapter.subscribe((jsonMessage, ackHandler) -> { }); // Verify that the queue has been declared with correct name and settings @@ -117,7 +117,7 @@ public void testUnsubscribe() throws IOException { String consumerTag = "my consumer tag"; when(mockChannel.basicConsume(anyString(), anyBoolean(), any(Consumer.class))).thenReturn(consumerTag); - adapter.subscribe(jsonMessage -> { + adapter.subscribe((jsonMessage, ackHandler) -> { }); adapter.unsubscribe(); diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java index 804d33f6..14444165 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java @@ -1,27 +1,28 @@ package io.github.tcdl.msb.adapters.amqp; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Envelope; -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.RejectedExecutionException; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Envelope; public class AmqpMessageConsumerTest { @@ -66,12 +67,7 @@ public void testMessageProcessing() throws IOException { AmqpMessageProcessingTask task = taskCaptor.getValue(); assertEquals(consumerTag, task.consumerTag); assertEquals(messageStr, task.body); - assertEquals(deliveryTag, task.deliveryTag); - assertEquals(mockMessageHandler, task.msgHandler); - assertEquals(mockChannel, task.channel); - - // verify that ack has been sent - mockChannel.basicAck(deliveryTag, false); + assertEquals(mockMessageHandler, task.msgHandler); } @Test diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java index 7a76ab05..98ac7cbb 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java @@ -1,18 +1,20 @@ package io.github.tcdl.msb.adapters.amqp; -import com.rabbitmq.client.Channel; -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; - import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import io.github.tcdl.msb.adapters.ConsumerAdapter; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; + +import com.rabbitmq.client.Channel; public class AmqpMessageProcessingTaskTest { @@ -20,26 +22,28 @@ public class AmqpMessageProcessingTaskTest { private Channel mockChannel; private ConsumerAdapter.RawMessageHandler mockMessageHandler; - + private AmqpAcknowledgementHandler mockAcknowledgementHandler; + private AmqpMessageProcessingTask task; @Before public void setUp() { mockChannel = mock(Channel.class); mockMessageHandler = mock(ConsumerAdapter.RawMessageHandler.class); - task = new AmqpMessageProcessingTask("consumer tag", messageStr, mockChannel, 123L, mockMessageHandler); + mockAcknowledgementHandler = mock(AmqpAcknowledgementHandler.class); + task = new AmqpMessageProcessingTask("consumer tag", messageStr, mockMessageHandler, mockAcknowledgementHandler); } @Test public void testMessageProcessing() throws IOException { task.run(); - verify(mockMessageHandler).onMessage(messageStr); + verify(mockMessageHandler).onMessage(messageStr, mockAcknowledgementHandler); } @Test public void testExceptionDuringProcessing() { - doThrow(new RuntimeException()).when(mockMessageHandler).onMessage(anyString()); + doThrow(new RuntimeException()).when(mockMessageHandler).onMessage(anyString(), any()); try { task.run(); diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java index 18709352..b7b00668 100644 --- a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java +++ b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java @@ -1,15 +1,17 @@ package io.github.tcdl.msb.cli; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.adapters.ConsumerAdapter.AcknowledgementHandler; +import io.github.tcdl.msb.api.exception.JsonConversionException; import java.io.IOException; import java.util.List; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + /** * This handler dumps messages from the given topics. * @@ -30,7 +32,7 @@ public CliMessageHandler(CliMessageSubscriber subscriber, List follow, b * @throws JsonConversionException if some problems during parsing JSON */ @Override - public void onMessage(String jsonMessage) { + public void onMessage(String jsonMessage, AcknowledgementHandler handler) { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); diff --git a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java index f5d9f18b..41704ae0 100644 --- a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java +++ b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java @@ -1,14 +1,14 @@ package io.github.tcdl.msb.cli; -import org.junit.Test; - -import java.util.Collections; - import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import java.util.Collections; + +import org.junit.Test; + public class CliMessageHandlerTest { @Test public void testSubscriptionToResponseQueue() { @@ -19,7 +19,7 @@ public void testSubscriptionToResponseQueue() { "{ \"topics\": {\n" + " \"to\": \"search:parsers:facets:v1\",\n" + " \"response\": \"search:parsers:facets:v1:response:3c3dec275b326c6500010843\"\n" - + " }}" + + " }}", null ); verify(subscriber).subscribe("search:parsers:facets:v1:response:3c3dec275b326c6500010843", handler); @@ -33,7 +33,7 @@ public void testNoSubscriptionIfMissingResponseQueue() { handler.onMessage( "{ \"topics\": {\n" + " \"to\": \"search:parsers:facets:v1\"\n" - + " }}" + + " }}", null ); verifyNoMoreInteractions(subscriber); @@ -48,7 +48,7 @@ public void testNoSubscriptionIfNullResponseQueue() { "{ \"topics\": {\n" + " \"to\": \"search:parsers:facets:v1\",\n" + " \"response\": null\n" - + " }}" + + " }}", null ); verifyNoMoreInteractions(subscriber); @@ -65,7 +65,7 @@ public void testSubscriptionNonExistingQueue() { "{ \"topics\": {\n" + " \"to\": \"search:parsers:facets:v1\",\n" + " \"response\": \"non-existent-queue\"\n" - + " }}" + + " }}", null ); // The point of this test is to verify that no exception is thrown in such case diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index 80b064c0..6c7bc921 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -1,6 +1,5 @@ package io.github.tcdl.msb; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.exception.JsonSchemaValidationException; @@ -10,14 +9,17 @@ import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.Utils; -import org.apache.commons.lang3.Validate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.time.Clock; import java.time.Instant; import java.time.temporal.ChronoUnit; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + /** * {@link Consumer} is a component responsible for consuming messages from the bus. */ @@ -82,7 +84,7 @@ public void end() { * * @param jsonMessage message to process */ - protected void handleRawMessage(String jsonMessage) { + protected void handleRawMessage(String jsonMessage, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { LOG.debug("Topic [{}] message received [{}]", this.topic, jsonMessage); channelMonitorAgent.consumerMessageReceived(topic); @@ -96,12 +98,14 @@ protected void handleRawMessage(String jsonMessage) { LOG.debug("Message has been successfully parsed {}", jsonMessage); if (!isMessageExpired(message)) { - messageHandler.handleMessage(message); + messageHandler.handleMessage(message, acknowledgeHandler); } else { LOG.warn("Expired message: {}", jsonMessage); + acknowledgeHandler.rejectMessage(); } } catch (JsonConversionException | JsonSchemaValidationException e) { LOG.error("Unable to process consumed message {}", jsonMessage, e); + acknowledgeHandler.rejectMessage(); } } diff --git a/core/src/main/java/io/github/tcdl/msb/MessageHandler.java b/core/src/main/java/io/github/tcdl/msb/MessageHandler.java index 05626021..1e664a06 100644 --- a/core/src/main/java/io/github/tcdl/msb/MessageHandler.java +++ b/core/src/main/java/io/github/tcdl/msb/MessageHandler.java @@ -1,5 +1,6 @@ package io.github.tcdl.msb; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.message.Message; public interface MessageHandler { @@ -8,6 +9,7 @@ public interface MessageHandler { * The handleMessage method is invoked when a message is successfully parsed and is ready for processing. * * @param message the message content + * @param acknowledgeHandler confirm/reject message handler */ - void handleMessage(Message message); + void handleMessage(Message message, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler); } diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java index 1024a498..48fe5320 100644 --- a/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java +++ b/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java @@ -31,6 +31,23 @@ interface RawMessageHandler { * * @param jsonMessage incomming JSON message */ - void onMessage(String jsonMessage); + void onMessage(String jsonMessage, AcknowledgementHandler handler); + } + + /** + * Callback interface for a message acknowledgement + * + */ + interface AcknowledgementHandler { + /** + * Server should consider messages acknowledged once delivered + */ + void confirmMessage(); + + /** + * Reject a message + */ + void rejectMessage(); + } } diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java index d4114ff9..a4350a60 100644 --- a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java +++ b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java @@ -1,14 +1,10 @@ package io.github.tcdl.msb.adapters.mock; -import com.fasterxml.jackson.databind.JsonNode; import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.adapters.ProducerAdapter; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.exception.JsonSchemaValidationException; import io.github.tcdl.msb.support.JsonValidator; -import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Map; @@ -18,6 +14,12 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + /** * MockAdapter class represents implementation of {@link ProducerAdapter} and {@link ConsumerAdapter} * for test purposes. @@ -83,7 +85,7 @@ public void subscribe(RawMessageHandler messageHandler) { if (messageHandler != null && jsonMessage != null) { LOG.debug("Process message for topic {} [{}]", topic, jsonMessage); - messageHandler.onMessage(jsonMessage); + messageHandler.onMessage(jsonMessage, null); } else { try { Thread.sleep(CONSUMING_INTERVAL); diff --git a/core/src/main/java/io/github/tcdl/msb/api/Requester.java b/core/src/main/java/io/github/tcdl/msb/api/Requester.java index 008b0524..56f931fe 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/Requester.java +++ b/core/src/main/java/io/github/tcdl/msb/api/Requester.java @@ -1,10 +1,13 @@ package io.github.tcdl.msb.api; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.exception.ChannelException; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.Message; +import java.util.function.BiConsumer; + /** * {@link Requester} enable user send message to bus and process responses for this messages if any expected. * @@ -66,7 +69,7 @@ public interface Requester { * @param acknowledgeHandler callback to be called * @return requester */ - Requester onAcknowledge(Callback acknowledgeHandler); + Requester onAcknowledge(BiConsumer acknowledgeHandler); /** * Registers a callback to be called when response {@link Message} with payload part set of type {@literal T} is received. @@ -75,7 +78,7 @@ public interface Requester { * @return requester * @throws JsonConversionException if unable to convert payload to type {@literal T} */ - Requester onResponse(Callback responseHandler); + Requester onResponse(BiConsumer responseHandler); /** * Registers a callback to be called when response {@link Message} with payload part set of is received. @@ -83,7 +86,7 @@ public interface Requester { * @param responseHandler callback to be called * @return requester */ - Requester onRawResponse(Callback responseHandler); + Requester onRawResponse(BiConsumer responseHandler); /** * Registers a callback to be called when all expected responses for request message are processes or awaiting timeout for responses occurred. diff --git a/core/src/main/java/io/github/tcdl/msb/api/Responder.java b/core/src/main/java/io/github/tcdl/msb/api/Responder.java index d8539859..f337b912 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/Responder.java +++ b/core/src/main/java/io/github/tcdl/msb/api/Responder.java @@ -1,11 +1,12 @@ package io.github.tcdl.msb.api; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.message.Message; /** * Responsible for creating responses and acknowledgements and sending them to the bus. */ -public interface Responder { +public interface Responder extends ConsumerAdapter.AcknowledgementHandler { /** * Send acknowledge message. diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index 3261cfa3..889afacc 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -2,6 +2,15 @@ import static io.github.tcdl.msb.support.Utils.ifNull; import static java.lang.Math.toIntExact; +import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.Callback; +import io.github.tcdl.msb.api.RequestOptions; +import io.github.tcdl.msb.api.message.Acknowledge; +import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.events.EventHandlers; +import io.github.tcdl.msb.impl.MsbContextImpl; +import io.github.tcdl.msb.support.Utils; + import java.time.Clock; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -11,19 +20,14 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ScheduledFuture; +import java.util.function.BiConsumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import io.github.tcdl.msb.api.Callback; -import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.message.Acknowledge; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.events.EventHandlers; -import io.github.tcdl.msb.impl.MsbContextImpl; -import io.github.tcdl.msb.support.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * {@link Collector} is a component which collects responses and acknowledgements for sent requests. @@ -55,9 +59,9 @@ public class Collector { private Clock clock; private Message requestMessage; - private Optional> onRawResponse = Optional.empty(); - private Optional> onResponse = Optional.empty(); - private Optional> onAcknowledge = Optional.empty(); + private Optional> onRawResponse = Optional.empty(); + private Optional> onResponse = Optional.empty(); + private Optional> onAcknowledge = Optional.empty(); private Optional> onEnd = Optional.empty(); private ScheduledFuture ackTimeoutFuture; @@ -118,23 +122,23 @@ public void listenForResponses() { collectorManager.registerCollector(this); } - public void handleMessage(Message incomingMessage) { + public void handleMessage(Message incomingMessage, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { LOG.debug("Received {}", incomingMessage); JsonNode rawPayload = incomingMessage.getRawPayload(); if (Utils.isPayloadPresent(rawPayload)) { LOG.debug("Received Payload {}", rawPayload); payloadMessages.add(incomingMessage); - onRawResponse.ifPresent(handler -> handler.call(incomingMessage)); + onRawResponse.ifPresent(handler -> handler.accept(incomingMessage, acknowledgeHandler)); T payload = Utils.convert(rawPayload, payloadTypeReference, payloadMapper); - onResponse.ifPresent(handler -> handler.call(payload)); + onResponse.ifPresent(handler -> handler.accept(payload, acknowledgeHandler)); incResponsesRemaining(-1); } else { LOG.debug("Received {}", incomingMessage.getAck()); ackMessages.add(incomingMessage); - onAcknowledge.ifPresent(handler -> handler.call((incomingMessage.getAck()))); + onAcknowledge.ifPresent(handler -> handler.accept(incomingMessage.getAck(), acknowledgeHandler)); } processAck(incomingMessage.getAck()); diff --git a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java index cb3edadd..3ce70047 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java @@ -2,14 +2,16 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.MessageHandler; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException; import io.github.tcdl.msb.api.message.Message; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Manages instances of {@link Collector}s that listens to the same response topic. */ @@ -32,11 +34,11 @@ public CollectorManager(String topic, ChannelManager channelManager) { * Determines correlationId from the incoming message and invokes the relevant {@link Collector} instance. */ @Override - public void handleMessage(Message message) { + public void handleMessage(Message message, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { String correlationId = message.getCorrelationId(); Collector collector = collectorsByCorrelationId.get(correlationId); if (collector != null) { - collector.handleMessage(message); + collector.handleMessage(message, acknowledgeHandler); } else { LOG.warn("Message with correlationId {} is not expected to be processed by any Collectors", correlationId); } diff --git a/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java b/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java index 1bd4ff7b..00d706cb 100644 --- a/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java +++ b/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java @@ -1,26 +1,29 @@ package io.github.tcdl.msb.events; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.Requester; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.Message; +import java.util.function.BiConsumer; + /** * {@link EventHandlers} is a component that allows to register custom event handlers for {@link Requester} specific events. */ public class EventHandlers { - private Callback onAcknowledge = acknowledge -> {}; - private Callback onResponse = response -> {}; - private Callback onRawResponse = response -> {}; + private BiConsumer onAcknowledge = (acknowledge, ackHandler) -> {}; + private BiConsumer onResponse = (acknowledge, ackHandler) -> {}; + private BiConsumer onRawResponse = (acknowledge, ackHandler) -> {}; private Callback onEnd = messages -> {}; - + /** * Return callback registered for Acknowledge event. * * @return acknowledge callback */ - public Callback onAcknowledge() { + public BiConsumer onAcknowledge() { return onAcknowledge; } @@ -29,7 +32,7 @@ public Callback onAcknowledge() { * * @param onAcknowledge callback */ - public EventHandlers onAcknowledge(Callback onAcknowledge) { + public EventHandlers onAcknowledge(BiConsumer onAcknowledge) { this.onAcknowledge = onAcknowledge; return this; } @@ -39,7 +42,7 @@ public EventHandlers onAcknowledge(Callback onAcknowledge) { * * @return response callback */ - public Callback onResponse() { + public BiConsumer onResponse() { return onResponse; } @@ -48,7 +51,7 @@ public Callback onResponse() { * * @param onResponse callback */ - public EventHandlers onResponse(Callback onResponse) { + public EventHandlers onResponse(BiConsumer onResponse) { this.onResponse = onResponse; return this; } @@ -58,7 +61,7 @@ public EventHandlers onResponse(Callback onResponse) { * * @return response callback */ - public Callback onRawResponse() { + public BiConsumer onRawResponse() { return onRawResponse; } @@ -67,7 +70,7 @@ public Callback onRawResponse() { * * @param onRawResponse callback */ - public EventHandlers onRawResponse(Callback onRawResponse) { + public EventHandlers onRawResponse(BiConsumer onRawResponse) { this.onRawResponse = onRawResponse; return this; } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java index 6c283377..355be9d7 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java @@ -1,7 +1,9 @@ package io.github.tcdl.msb.impl; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Message; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,9 +14,11 @@ public class NoopResponderImpl implements Responder { private static final Logger LOG = LoggerFactory.getLogger(NoopResponderImpl.class); private Message originalMessage; + private ConsumerAdapter.AcknowledgementHandler ackHandler; - public NoopResponderImpl(Message originalMessage) { + public NoopResponderImpl(Message originalMessage, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { this.originalMessage = originalMessage; + this.ackHandler = ackHandler; } /** {@inheritDoc} */ @@ -34,4 +38,15 @@ public void send(Object responsePayload) { public Message getOriginalMessage() { return originalMessage; } + + @Override + public void confirmMessage() { + ackHandler.confirmMessage(); + } + + @Override + public void rejectMessage() { + ackHandler.rejectMessage(); + } + } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java index 0a6967b8..5ff2e355 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java @@ -1,7 +1,7 @@ package io.github.tcdl.msb.impl; -import com.fasterxml.jackson.core.type.TypeReference; import io.github.tcdl.msb.ChannelManager; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.RequestOptions; @@ -11,8 +11,13 @@ import io.github.tcdl.msb.collector.Collector; import io.github.tcdl.msb.events.EventHandlers; import io.github.tcdl.msb.message.MessageFactory; + +import java.util.function.BiConsumer; + import org.apache.commons.lang3.Validate; +import com.fasterxml.jackson.core.type.TypeReference; + /** * Implementation of {@link Requester} * @@ -121,7 +126,7 @@ private boolean isWaitForResponses() { * {@inheritDoc} */ @Override - public Requester onAcknowledge(Callback acknowledgeHandler) { + public Requester onAcknowledge(BiConsumer acknowledgeHandler) { eventHandlers.onAcknowledge(acknowledgeHandler); return this; } @@ -130,7 +135,7 @@ public Requester onAcknowledge(Callback acknowledgeHandler) { * {@inheritDoc} */ @Override - public Requester onResponse(Callback responseHandler) { + public Requester onResponse(BiConsumer responseHandler) { eventHandlers.onResponse(responseHandler); return this; } @@ -138,7 +143,7 @@ public Requester onResponse(Callback responseHandler) { /** * {@inheritDoc} */ - @Override public Requester onRawResponse(Callback responseHandler) { + @Override public Requester onRawResponse(BiConsumer responseHandler) { eventHandlers.onRawResponse(responseHandler); return this; } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java index e8f289b2..f196053b 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java @@ -2,17 +2,18 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.Producer; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.Responder; +import io.github.tcdl.msb.api.message.Acknowledge.Builder; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.message.MessageFactory; import io.github.tcdl.msb.support.Utils; + import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static io.github.tcdl.msb.api.message.Acknowledge.Builder; - public class ResponderImpl implements Responder { private static final Logger LOG = LoggerFactory.getLogger(ResponderImpl.class); @@ -22,11 +23,14 @@ public class ResponderImpl implements Responder { private ChannelManager channelManager; private MessageFactory messageFactory; private Message.Builder messageBuilder; + private ConsumerAdapter.AcknowledgementHandler ackHandler; - public ResponderImpl(MessageTemplate messageTemplate, Message originalMessage, MsbContextImpl msbContext) { + public ResponderImpl(MessageTemplate messageTemplate, Message originalMessage, + ConsumerAdapter.AcknowledgementHandler ackHandler, MsbContextImpl msbContext) { validateReceivedMessage(originalMessage); this.responderId = Utils.generateId(); this.originalMessage = originalMessage; + this.ackHandler = ackHandler; this.channelManager = msbContext.getChannelManager(); this.messageFactory = msbContext.getMessageFactory(); this.messageBuilder = messageFactory.createResponseMessageBuilder(messageTemplate, originalMessage); @@ -74,4 +78,15 @@ private void validateReceivedMessage(Message originalMessage) { public Message getOriginalMessage() { return this.originalMessage; } + + @Override + public void confirmMessage() { + ackHandler.confirmMessage(); + } + + @Override + public void rejectMessage() { + ackHandler.rejectMessage(); + } + } \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java index 0e454c1a..81a0a759 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java @@ -1,8 +1,7 @@ package io.github.tcdl.msb.impl; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.ChannelManager; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.ResponderServer; @@ -10,10 +9,14 @@ import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.support.Utils; + import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + public class ResponderServerImpl implements ResponderServer { private static final Logger LOG = LoggerFactory.getLogger(ResponderServerImpl.class); @@ -55,20 +58,20 @@ public ResponderServer listen() { ChannelManager channelManager = msbContext.getChannelManager(); channelManager.subscribe(namespace, - incomingMessage -> { + (incomingMessage, acknowledgeHandler) -> { LOG.debug("[{}] Received message with id: [{}]", namespace, incomingMessage.getId()); - Responder responder = createResponder(incomingMessage); + Responder responder = createResponder(incomingMessage, acknowledgeHandler); onResponder(responder); }); return this; } - Responder createResponder(Message incomingMessage) { + Responder createResponder(Message incomingMessage, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { if (isResponseNeeded(incomingMessage)) { - return new ResponderImpl(messageTemplate, incomingMessage, msbContext); + return new ResponderImpl(messageTemplate, incomingMessage, acknowledgeHandler, msbContext); } else { - return new NoopResponderImpl(incomingMessage); + return new NoopResponderImpl(incomingMessage, acknowledgeHandler); } } diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java b/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java index d29e02f0..e7217e9a 100644 --- a/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java +++ b/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java @@ -57,8 +57,8 @@ public static void start(MsbContextImpl msbContext) { */ public DefaultChannelMonitorAgent start() { channelManager.subscribe(Utils.TOPIC_HEARTBEAT, // Launch listener for heartbeat topic - message -> { - Responder responder = new ResponderImpl(null, message, msbContext); + (message, acknowledgeHandler) -> { + Responder responder = new ResponderImpl(null, message, acknowledgeHandler, msbContext); RestPayload payload = new RestPayload.Builder>() .withBody(topicInfoMap) .build(); diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java b/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java index ba9f1973..4951314d 100644 --- a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java +++ b/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java @@ -1,16 +1,8 @@ package io.github.tcdl.msb.monitor.aggregator; import static io.github.tcdl.msb.support.Utils.TOPIC_ANNOUNCE; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.ChannelManager; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.ObjectFactory; import io.github.tcdl.msb.api.exception.JsonConversionException; @@ -24,9 +16,20 @@ import io.github.tcdl.msb.impl.MsbContextImpl; import io.github.tcdl.msb.monitor.agent.AgentTopicStats; import io.github.tcdl.msb.support.Utils; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + public class DefaultChannelMonitorAggregator implements ChannelMonitorAggregator { private static final Logger LOG = LoggerFactory.getLogger(DefaultChannelMonitorAggregator.class); @@ -85,7 +88,7 @@ void onHeartbeatResponses(List heartbeatResponses) { } } - void onAnnounce(Message announcementMessage) { + void onAnnounce(Message announcementMessage, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { LOG.debug(String.format("Handling announcement message %s...", announcementMessage)); boolean successfullyAggregated = aggregateInfo(masterAggregatorStats, announcementMessage); diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTask.java b/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTask.java index acee6df1..448b82d7 100644 --- a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTask.java +++ b/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTask.java @@ -6,13 +6,14 @@ import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.support.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Sends heartbeat requests and passes the aggregated responses to the registered handler. This task is meant to be scheduled periodically. */ @@ -43,7 +44,7 @@ public void run() { List messages = Collections.synchronizedList(new LinkedList<>()); objectFactory.createRequester(Utils.TOPIC_HEARTBEAT, requestOptions) - .onRawResponse(messages::add) + .onRawResponse((message, achHandler) -> {messages.add(message);}) .onEnd(end -> heartbeatHandler.call(messages)) .publish(emptyPayload); diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java index 8a6b4545..261b0387 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java @@ -6,12 +6,6 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import java.time.Clock; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.googlecode.junittoolbox.MultithreadingTester; import io.github.tcdl.msb.api.RequesterResponderIT; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.collector.CollectorManager; @@ -19,9 +13,17 @@ import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.TestUtils; + +import java.time.Clock; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + import org.junit.Before; import org.junit.Test; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.googlecode.junittoolbox.MultithreadingTester; + public class ChannelManagerConcurrentTest { private ChannelManager channelManager; @@ -97,7 +99,7 @@ public void testReceiveMessageInvokesAgentAndEmitsEventMultithreadInteraction() channelManager.findOrCreateProducer(topic); channelManager.subscribe(topic, - msg -> { + (msg, acknowledgeHandler) -> { messagesReceived.countDown(); }); diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java index 02274c55..147a42ea 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java @@ -7,21 +7,24 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import javax.xml.ws.Holder; -import java.time.Clock; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.TestUtils; + +import java.time.Clock; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.xml.ws.Holder; + import org.junit.Before; import org.junit.Test; +import com.fasterxml.jackson.databind.ObjectMapper; + public class ChannelManagerTest { private ChannelManager channelManager; @@ -60,8 +63,8 @@ public void testConsumerSubscribeMultipleSameTopic() { String topic = "topic:test-consumer-cached"; // Consumer was created and monitor agent notified - channelManager.subscribe(topic, message -> {}); - channelManager.subscribe(topic, message -> {}); + channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); + channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); } @Test @@ -85,7 +88,7 @@ public void testReceiveMessageInvokesAgentAndEmitsEvent() throws InterruptedExce Message message = TestUtils.createSimpleRequestMessage(topic); channelManager.findOrCreateProducer(topic).publish(message); channelManager.subscribe(topic, - msg -> { + (msg, acknowledgeHandler) -> { messageEvent.value = msg; awaitReceiveEvents.countDown(); }); @@ -99,7 +102,7 @@ public void testReceiveMessageInvokesAgentAndEmitsEvent() throws InterruptedExce public void testSubscribeUnsubscribe() { String topic = "topic:test-unsubscribe-once"; - channelManager.subscribe(topic, message -> {}); + channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); channelManager.unsubscribe(topic); verify(mockChannelMonitorAgent).consumerTopicRemoved(topic); @@ -110,8 +113,8 @@ public void testSubscribeUnsubscribeSeparateTopics() { String topic1 = "topic:test-unsubscribe-try-first"; String topic2 = "topic:test-unsubscribe-try-other"; - channelManager.subscribe(topic1, message -> {}); - channelManager.subscribe(topic2, message -> {}); + channelManager.subscribe(topic1, (message, acknowledgeHandler) -> {}); + channelManager.subscribe(topic2, (message, acknowledgeHandler) -> {}); channelManager.unsubscribe(topic1); verify(mockChannelMonitorAgent).consumerTopicRemoved(topic1); diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index 54d15120..f5d2740e 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -1,7 +1,10 @@ package io.github.tcdl.msb; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.typesafe.config.ConfigFactory; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Message; @@ -12,20 +15,18 @@ import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; import java.time.Clock; import java.time.Instant; import java.time.ZoneId; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.typesafe.config.ConfigFactory; @RunWith(MockitoJUnitRunner.class) public class ConsumerTest { @@ -102,18 +103,18 @@ public void testValidMessageProcessedBySubscriber() throws JsonConversionExcepti Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper)); + consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), null); - verify(messageHandlerMock).handleMessage(any(Message.class)); + verify(messageHandlerMock).handleMessage(any(Message.class), any()); } @Test public void testExceptionWhileMessageConvertingProcessedBySubscriber() throws JsonConversionException { Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage("{\"body\":\"fake message\"}"); + consumer.handleRawMessage("{\"body\":\"fake message\"}", createAcknowledgementHandler()); - verify(messageHandlerMock, never()).handleMessage(any(Message.class)); + verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); } @Test @@ -128,10 +129,10 @@ public void testHandleRawMessageConsumeFromTopicSkipValidation() { // create a message with required empty namespace Message message = TestUtils.createSimpleRequestMessage(""); - consumer.handleRawMessage(Utils.toJson(message, messageMapper)); + consumer.handleRawMessage(Utils.toJson(message, messageMapper), null); // should skip validation and process it - verify(messageHandlerMock).handleMessage(any(Message.class)); + verify(messageHandlerMock).handleMessage(any(Message.class), any()); } @@ -140,8 +141,8 @@ public void testHandleRawMessageConsumeFromTopic() throws JsonConversionExceptio Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper)); - verify(messageHandlerMock).handleMessage(any(Message.class)); + consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), null); + verify(messageHandlerMock).handleMessage(any(Message.class), any()); } @Test @@ -149,8 +150,8 @@ public void testHandleRawMessageConsumeFromTopicValidateThrowException() { MsbConfig msbConf = TestUtils.createMsbConfigurations(); Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage("{\"body\":\"fake message\"}"); - verify(messageHandlerMock, never()).handleMessage(any(Message.class)); // no processing + consumer.handleRawMessage("{\"body\":\"fake message\"}", createAcknowledgementHandler()); + verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); // no processing } @Test @@ -159,8 +160,8 @@ public void testHandleRawMessageConsumeFromServiceTopicValidateThrowException() MsbConfig msbConf = TestUtils.createMsbConfigurations(); Consumer consumer = new Consumer(adapterMock, service_topic, messageHandlerMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage("{\"body\":\"fake message\"}"); - verify(messageHandlerMock, never()).handleMessage(any(Message.class)); // no processing + consumer.handleRawMessage("{\"body\":\"fake message\"}", createAcknowledgementHandler()); + verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); // no processing } @Test @@ -168,8 +169,8 @@ public void testHandleRawMessageConsumeFromTopicExpiredMessage() throws JsonConv Message expiredMessage = createExpiredMsbRequestMessageWithTopicTo(TOPIC); Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper)); - verify(messageHandlerMock, never()).handleMessage(any(Message.class)); + consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper), createAcknowledgementHandler()); + verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); } private Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) { @@ -183,4 +184,16 @@ private Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) { return new Message.Builder().withCorrelationId(Utils.generateId()).withId(Utils.generateId()).withTopics(topic).withMetaBuilder(metaBuilder) .build(); } + + private ConsumerAdapter.AcknowledgementHandler createAcknowledgementHandler() { + return new ConsumerAdapter.AcknowledgementHandler() { + @Override + public void confirmMessage() { + } + + @Override + public void rejectMessage() { + } + }; + } } \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterTest.java b/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterTest.java index 39f3542e..5d37e038 100644 --- a/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterTest.java +++ b/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterTest.java @@ -2,16 +2,11 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; -import java.util.LinkedList; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutorService; - -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.adapters.ProducerAdapter; import io.github.tcdl.msb.api.exception.ChannelException; @@ -19,8 +14,16 @@ import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; + import org.junit.Test; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * MockAdapter class represents implementation of {@link ProducerAdapter} and {@link ConsumerAdapter} * for test purposes. @@ -55,7 +58,7 @@ public void testSubscribeCallMessageHandler() throws ChannelException, JsonConve mockAdapter.subscribe(mockHandler); assertTrue(activeConsumerExecutors.size() == 1); - verify(mockHandler, timeout(500)).onMessage(eq(message)); + verify(mockHandler, timeout(500)).onMessage(eq(message), any()); } @Test diff --git a/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java b/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java index 804e2ac4..1449ab7b 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java @@ -3,14 +3,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.time.Instant; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - import io.github.tcdl.msb.adapters.mock.MockAdapter; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; @@ -22,6 +14,15 @@ import io.github.tcdl.msb.monitor.agent.DefaultChannelMonitorAgent; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; + +import java.time.Instant; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -164,7 +165,7 @@ private Message awaitHeartBeatRequestSent() throws InterruptedException { //need to await for original request for heartbeat to be send to simulate response with same correlationId CountDownLatch awaitRequestMessage = new CountDownLatch(1); List outgoingRequestMessages = new LinkedList<>(); - msbContext.getChannelManager().subscribe(Utils.TOPIC_HEARTBEAT, message -> { + msbContext.getChannelManager().subscribe(Utils.TOPIC_HEARTBEAT, (message, acknowledgeHandler) -> { outgoingRequestMessages.add(message); awaitRequestMessage.countDown(); }); diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java index 3b154678..af56e0a2 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java @@ -1,14 +1,13 @@ package io.github.tcdl.msb.api; -import com.fasterxml.jackson.databind.JsonNode; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import io.github.tcdl.msb.adapters.mock.MockAdapter; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.impl.MsbContextImpl; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; -import org.junit.Before; -import org.junit.Test; import java.util.HashSet; import java.util.LinkedList; @@ -20,8 +19,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.databind.JsonNode; public class RequesterResponderIT { @@ -78,8 +79,8 @@ public void testResponderAnswerWithAckRequesterReceiveAck() throws Exception { //Create and send request message directly to broker, wait for ack RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); msbContext.getObjectFactory().createRequester(namespace, requestOptions). - onAcknowledge((Acknowledge ack) -> { - receivedResponseAcks.add(ack); + onAcknowledge((ackMessage, ackHandler) -> { + receivedResponseAcks.add(ackMessage); ackResponseReceived.countDown(); }) .publish(requestPayload); @@ -115,7 +116,7 @@ public void testResponderAnswerWithResponseRequesterReceiveCustomPayloadResponse String responsePayload = "response payload"; //Create and send request message directly to broker, wait for response msbContext.getObjectFactory().createRequester(namespace, requestOptions, String.class) - .onResponse(payload -> { + .onResponse((payload, ackHandler) -> { receivedResponses.add(payload); respReceived.countDown(); assertEquals(responsePayload, payload); @@ -156,7 +157,7 @@ public void testResponderCommunicationWithAck() throws Exception { //Create and send request message, wait for ack Requester requester = msbContext.getObjectFactory().createRequester(namespace2, requestAwaitAckMessageOptions); RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); - requester.onAcknowledge((Acknowledge a) -> ackReceived.countDown()); + requester.onAcknowledge((ackMessage, achHandler) -> ackReceived.countDown()); requester.publish(requestPayload); }) .listen(); @@ -199,7 +200,7 @@ public void testMultipleRequesterListenForAcks() throws Exception { while (messagesToSend.get() > 0) { msbContext.getObjectFactory().createRequester(namespace, requestOptions). - onAcknowledge((Acknowledge ack) -> { + onAcknowledge((ack, ackHanlder) -> { receivedResponseAcks.add(ack); ackResponseReceived.countDown(); }) diff --git a/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java index 219a4162..48fcb952 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java @@ -1,7 +1,10 @@ package io.github.tcdl.msb.api; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import io.github.tcdl.msb.adapters.mock.MockAdapter; import io.github.tcdl.msb.api.exception.JsonSchemaValidationException; import io.github.tcdl.msb.api.message.Message; @@ -10,18 +13,16 @@ import io.github.tcdl.msb.impl.ResponderImpl; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.TestUtils; + +import java.io.IOException; + import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; public class ResponderIT { @@ -48,7 +49,7 @@ public void testCreateAckMessage() throws Exception { MessageTemplate messageOptions = TestUtils.createSimpleMessageTemplate(); Message originalMessage = TestUtils.createSimpleRequestMessage(namespace); - Responder responder = new ResponderImpl(messageOptions, originalMessage, msbContext); + Responder responder = new ResponderImpl(messageOptions, originalMessage, null, msbContext); responder.sendAck(ackTimeout, responsesRemaining); @@ -89,7 +90,7 @@ public void testCreateResponseMessage() throws Exception { MessageTemplate messageOptions = TestUtils.createSimpleMessageTemplate(); Message originalMessage = TestUtils.createSimpleRequestMessage(namespace); - Responder responder = new ResponderImpl(messageOptions, originalMessage, msbContext); + Responder responder = new ResponderImpl(messageOptions, originalMessage, null, msbContext); RestPayload responsePayload = TestUtils.createSimpleResponsePayload(); responder.send(responsePayload); @@ -106,7 +107,7 @@ public void testCreateResponseMessageWithTags() throws Exception { String dynamicTagOriginal = "dynamic-tag-original"; Message originalMessage = TestUtils.createSimpleRequestMessageWithTags(namespace, dynamicTagOriginal); - Responder responder = new ResponderImpl(messageOptions, originalMessage, msbContext); + Responder responder = new ResponderImpl(messageOptions, originalMessage, null, msbContext); RestPayload responsePayload = TestUtils.createSimpleResponsePayload(); responder.send(responsePayload); diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java index 90763758..ef41babb 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java @@ -1,22 +1,22 @@ package io.github.tcdl.msb.collector; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.support.TestUtils; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @RunWith(MockitoJUnitRunner.class) public class CollectorManagerTest { @@ -39,9 +39,9 @@ public void testHandleMessageRegisteredCollectorForTopic() { when(collectorMock.getRequestMessage()).thenReturn(originalAndReceivedMessage); CollectorManager collectorManager = new CollectorManager(TOPIC, channelManagerMock); collectorManager.registerCollector(collectorMock); - collectorManager.handleMessage(originalAndReceivedMessage); + collectorManager.handleMessage(originalAndReceivedMessage, null); - verify(collectorMock).handleMessage(originalAndReceivedMessage); + verify(collectorMock).handleMessage(originalAndReceivedMessage, null); } @Test @@ -49,9 +49,9 @@ public void testHandleMessageRegisteredCollectorForTopicUnexpectedCorrelationId( Message receivedMessage = TestUtils.createSimpleRequestMessage(TOPIC); CollectorManager collectorManager = new CollectorManager(TOPIC, channelManagerMock); collectorManager.registerCollector(collectorMock); - collectorManager.handleMessage(receivedMessage); + collectorManager.handleMessage(receivedMessage, null); - verify(collectorMock, never()).handleMessage(receivedMessage); + verify(collectorMock, never()).handleMessage(receivedMessage, null); } @Test @@ -61,9 +61,9 @@ public void testHandleMessageUnregisteredProperCollectorForTopic() { CollectorManager collectorManager = new CollectorManager("some-other-topic", channelManagerMock); collectorManager.registerCollector(collectorMock); - collectorManager.handleMessage(receivedMessage); + collectorManager.handleMessage(receivedMessage, null); - verify(collectorMock, never()).handleMessage(receivedMessage); + verify(collectorMock, never()).handleMessage(receivedMessage, null); } @Test diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java index 5a435650..90984ecd 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java @@ -14,18 +14,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.io.IOException; -import java.time.Clock; -import java.util.concurrent.ScheduledFuture; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.ChannelManager; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.Message; @@ -33,10 +25,15 @@ import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.events.EventHandlers; import io.github.tcdl.msb.impl.MsbContextImpl; -import io.github.tcdl.msb.impl.ResponderImpl; import io.github.tcdl.msb.message.MessageFactory; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; + +import java.io.IOException; +import java.time.Clock; +import java.util.concurrent.ScheduledFuture; +import java.util.function.BiConsumer; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,6 +41,10 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + @RunWith(MockitoJUnitRunner.class) public class CollectorTest { @@ -150,23 +151,25 @@ public void testHandleResponse() { String bodyText = "some body"; Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); @SuppressWarnings("unchecked") - Callback onResponse = mock(Callback.class); + BiConsumer onResponse = mock(BiConsumer.class); @SuppressWarnings("unchecked") - Callback onRawResponse = mock(Callback.class); + BiConsumer onRawResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); when(eventHandlers.onRawResponse()).thenReturn(onRawResponse); Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() { }); + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + // method under test - collector.handleMessage(responseMessage); + collector.handleMessage(responseMessage, ackHandler); RestPayload expectedPayload = new RestPayload.Builder() .withBody(bodyText) .build(); - verify(onRawResponse).call(responseMessage); - verify(onResponse).call(expectedPayload); + verify(onRawResponse).accept(responseMessage, ackHandler); + verify(onResponse).accept(expectedPayload, ackHandler); verify(collectorManagerMock).unregisterCollector(collector); assertTrue(collector.getPayloadMessages().stream().anyMatch(message -> message.getId().equals(responseMessage.getId()))); assertFalse(collector.getAckMessages().contains(responseMessage)); @@ -177,9 +180,9 @@ public void testHandleResponseConversionFailed() { String bodyText = "some body"; Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); @SuppressWarnings("unchecked") - Callback> onResponse = mock(Callback.class); + BiConsumer, ConsumerAdapter.AcknowledgementHandler> onResponse = mock(BiConsumer.class); @SuppressWarnings("unchecked") - Callback onRawResponse = mock(Callback.class); + BiConsumer onRawResponse = mock(BiConsumer.class); @SuppressWarnings("unchecked") EventHandlers> eventHandlers = mock(EventHandlers.class); @@ -190,25 +193,28 @@ public void testHandleResponseConversionFailed() { Collector> collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, payloadTypeReference); + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + // make sure that onRawResponse is called even if conversion of payload to custom type fails try { - collector.handleMessage(responseMessage); + collector.handleMessage(responseMessage, ackHandler); } finally { - verify(onRawResponse).call(responseMessage); - verify(onResponse, never()).call(any()); + verify(onRawResponse).accept(responseMessage, ackHandler); + verify(onResponse, never()).accept(any(), any()); } } @Test @SuppressWarnings({ "rawtypes", "unchecked" }) public void testHandleResponseReceivedAck() { - Callback onAck = mock(Callback.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Collector collector = createCollector(); + + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + collector.handleMessage(responseMessageWithAck, ackHandler); - collector.handleMessage(responseMessageWithAck); - - verify(onAck).call(responseMessageWithAck.getAck()); + verify(onAck).accept(responseMessageWithAck.getAck(), ackHandler); assertTrue(collector.getAckMessages().contains(responseMessageWithAck)); assertFalse(collector.getPayloadMessages().contains(responseMessageWithAck)); } @@ -220,7 +226,8 @@ public void testHandleResponseEndEventNoResponsesRemaining() { when(eventHandlers.onEnd()).thenReturn(onEnd); Collector collector = createCollector(); - collector.handleMessage(responseMessageWithAck); + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + collector.handleMessage(responseMessageWithAck, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(anyInt(), any(Collector.class)); verify(timeoutManagerMock, never()).enableAckTimeout(anyInt(), any(Collector.class)); @@ -240,18 +247,19 @@ public void testHandleResponseLastResponse() { when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout); when(requestOptionsMock.getWaitForResponses()).thenReturn(1); - Callback onResponse = mock(Callback.class); + BiConsumer onResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); Collector collector = createCollector(); - collector.handleMessage(responseMessage); + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + collector.handleMessage(responseMessage, ackHandler); RestPayload expectedPayload = new RestPayload.Builder() .withBody(bodyText) .build(); - verify(onResponse).call(expectedPayload); + verify(onResponse).accept(expectedPayload, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(responseTimeout), eq(collector)); verify(timeoutManagerMock, never()).enableAckTimeout(eq(0), eq(collector)); verify(onEnd).call(any()); @@ -270,7 +278,7 @@ public void testHandleResponseWaitForOneMoreResponse() { when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout); when(requestOptionsMock.getWaitForResponses()).thenReturn(2); - Callback onResponse = mock(Callback.class); + BiConsumer onResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -282,14 +290,15 @@ public void testHandleResponseWaitForOneMoreResponse() { .withBody(bodyText) .build(); + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); //send first response - collector.handleMessage(responseMessage); - verify(onResponse).call(expectedPayload); + collector.handleMessage(responseMessage, ackHandler); + verify(onResponse).accept(expectedPayload, ackHandler); verify(onEnd, never()).call(any()); //send last response - collector.handleMessage(responseMessage); - verify(onResponse, times(2)).call(expectedPayload); + collector.handleMessage(responseMessage, ackHandler); + verify(onResponse, times(2)).accept(expectedPayload, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(responseTimeout), eq(collector)); verify(timeoutManagerMock, never()).enableAckTimeout(eq(0), eq(collector)); verify(onEnd).call(any()); @@ -308,7 +317,8 @@ public void testHandleResponseNoResponsesRemainingButAwaitAck() { when(requestOptionsMock.getResponseTimeout()).thenReturn(0); when(requestOptionsMock.getWaitForResponses()).thenReturn(0); - Callback onAck = mock(Callback.class); + BiConsumer onAck = mock(BiConsumer.class); + when(eventHandlers.onAcknowledge()).thenReturn(onAck); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -316,8 +326,9 @@ public void testHandleResponseNoResponsesRemainingButAwaitAck() { Collector collector = createCollector(); collector.listenForResponses(); + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); //send payload response - collector.handleMessage(responseMessage); + collector.handleMessage(responseMessage, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(anyInt(), eq(collector)); verify(timeoutManagerMock).enableAckTimeout(anyInt(), eq(collector)); verify(onEnd, never()).call(any()); @@ -336,7 +347,7 @@ public void testHandleResponseReceivedPayloadButAwaitAck() { when(requestOptionsMock.getResponseTimeout()).thenReturn(0); when(requestOptionsMock.getWaitForResponses()).thenReturn(1); - Callback onAck = mock(Callback.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -344,8 +355,9 @@ public void testHandleResponseReceivedPayloadButAwaitAck() { Collector collector = createCollector(); collector.listenForResponses(); + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); //send payload response - collector.handleMessage(responseMessage); + collector.handleMessage(responseMessage, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(0), eq(collector)); verify(timeoutManagerMock).enableAckTimeout(anyInt(), eq(collector)); verify(onEnd, never()).call(any()); @@ -364,7 +376,7 @@ public void testHandleResponseNoResponsesRemainingAndWaitUntilAckBeforeNow() { when(requestOptionsMock.getResponseTimeout()).thenReturn(0); when(requestOptionsMock.getWaitForResponses()).thenReturn(0); - Callback onAck = mock(Callback.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -372,8 +384,9 @@ public void testHandleResponseNoResponsesRemainingAndWaitUntilAckBeforeNow() { Collector collector = createCollector(); collector.listenForResponses(); + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); //send payload response - collector.handleMessage(responseMessage); + collector.handleMessage(responseMessage, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(0), eq(collector)); verify(timeoutManagerMock, never()).enableAckTimeout(eq(ackTimeoutMs), eq(collector)); verify(onEnd).call(any()); @@ -398,7 +411,8 @@ public void testHandleResponseReceivedAckWithSameTimeoutValue() { Acknowledge ack = new Acknowledge.Builder().withResponderId(Utils.generateId()).withResponsesRemaining(0).withTimeoutMs(timeoutMs).build(); Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); - collector.handleMessage(responseMessageWithAck); + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + collector.handleMessage(responseMessageWithAck, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(timeoutMs), eq(collector)); verify(onEnd).call(any()); @@ -425,7 +439,8 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndNoResponsesRemaini .build(); Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); - collector.handleMessage(responseMessageWithAck); + ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + collector.handleMessage(responseMessageWithAck, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(timeoutMsInAck), eq(collector)); verify(onEnd).call(any()); @@ -454,7 +469,8 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndResponsesRemaining .withTimeoutMs(timeoutMsInAck).build(); Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); - collector.handleMessage(responseMessageWithAck); + + collector.handleMessage(responseMessageWithAck, null); verify(timeoutManagerMock).enableResponseTimeout(timeoutCaptor.capture(), any()); @@ -493,13 +509,13 @@ public void testHandleResponseReceivedAcksWithUpdatedTimeoutAndResponsesRemainin Message messageWithAckTwo = TestUtils .createMsbResponseMessageWithAckNoPayload(ackRespTwo, TOPIC_RESPONSE, originalMessage.getCorrelationId()); - collector.handleMessage(messageWithAckOne); + collector.handleMessage(messageWithAckOne, null); verify(timeoutManagerMock).enableResponseTimeout(timeoutCaptor.capture(), any()); assertEquals(responsesRemainingResponderOne, collector.getResponsesRemaining()); - assertThat(timeoutCaptor.getValue()).isBetween(1, timeoutMsInAckResponderOne - 1); + assertThat(timeoutCaptor.getValue()).isBetween(1, timeoutMsInAckResponderOne); verify(onEnd, never()).call(any()); - collector.handleMessage(messageWithAckTwo); + collector.handleMessage(messageWithAckTwo, null); verify(timeoutManagerMock, times(2)).enableResponseTimeout(timeoutCaptor.capture(), any()); assertEquals(responsesRemainingResponderOne + responsesRemainingResponderTwo, collector.getResponsesRemaining()); assertThat(timeoutCaptor.getValue()).isBetween(1, timeoutMsInAckResponderTwo - 1); @@ -520,7 +536,7 @@ public void testHandleResponseEnsureResponsesRemainingIsDecreased() { when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout); when(requestOptionsMock.getWaitForResponses()).thenReturn(responsesRemaining); - Callback onResponse = mock(Callback.class); + BiConsumer onResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -531,12 +547,12 @@ public void testHandleResponseEnsureResponsesRemainingIsDecreased() { assertEquals(responsesRemaining, collector.getResponsesRemaining()); //send first response - collector.handleMessage(responseMessage); + collector.handleMessage(responseMessage, null); assertEquals(1, collector.getResponsesRemaining()); verify(onEnd, never()).call(any()); //send last response - collector.handleMessage(responseMessage); + collector.handleMessage(responseMessage, null); assertEquals(0, collector.getResponsesRemaining()); verify(onEnd).call(any()); } @@ -570,7 +586,7 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndOneResponseRemaini Acknowledge ack = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(1).withTimeoutMs(timeoutMsInAck) .build(); Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); - collector.handleMessage(responseMessageWithAck); + collector.handleMessage(responseMessageWithAck, null); //simulate responder response Acknowledge responseAck = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(-1).build(); @@ -580,7 +596,7 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndOneResponseRemaini Message responderMessage = TestUtils.createMsbResponseMessage(responseAck, payloadNode, TOPIC_RESPONSE, "someCorrelationId"); //send message - collector.handleMessage(responderMessage); + collector.handleMessage(responderMessage, null); verify(onEnd, timeout(1500)).call(any()); } @@ -614,7 +630,7 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndTwoResponsesRemain Acknowledge ack = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(2).withTimeoutMs(timeoutMsInAck) .build(); Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); - collector.handleMessage(responseMessageWithAck); + collector.handleMessage(responseMessageWithAck, null); //simulate responder response Acknowledge responseAck = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(-1).build(); @@ -624,11 +640,11 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndTwoResponsesRemain Message responderMessage = TestUtils.createMsbResponseMessage(responseAck, payloadNode, TOPIC_RESPONSE, "someCorrelationId"); //send first message - collector.handleMessage(responderMessage); + collector.handleMessage(responderMessage, null); //send second message after initial response Thread.sleep(200); - collector.handleMessage(responderMessage); + collector.handleMessage(responderMessage, null); verify(onEnd, timeout(1500)).call(any()); } diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index 4fdacb9f..3c253020 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -1,6 +1,19 @@ package io.github.tcdl.msb.impl; -import com.fasterxml.jackson.core.type.TypeReference; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.Consumer; import io.github.tcdl.msb.Producer; @@ -11,30 +24,18 @@ import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.collector.Collector; -import io.github.tcdl.msb.events.EventHandlers; import io.github.tcdl.msb.support.TestUtils; + +import java.time.Clock; +import java.util.function.BiConsumer; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import java.time.Clock; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import com.fasterxml.jackson.core.type.TypeReference; /** * Created by rdro on 4/27/2015. @@ -83,7 +84,7 @@ public void testPublishWaitForResponsesAck() throws Exception { requester.publish(TestUtils.createSimpleRequestPayload()); Message responseMessage = TestUtils.createMsbRequestMessage("some:topic", "body text"); - collectorMock.handleMessage(responseMessage); + collectorMock.handleMessage(responseMessage, null); } @Test @@ -102,7 +103,7 @@ public void testProducerPublishWithPayload() throws Exception { @Test @SuppressWarnings("unchecked") public void testAcknowledgeEventHandlerIsAdded() throws Exception { - Callback onAckMock = mock(Callback.class); + BiConsumer onAckMock = mock(BiConsumer.class); RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null); requester.onAcknowledge(onAckMock); @@ -116,7 +117,7 @@ public void testAcknowledgeEventHandlerIsAdded() throws Exception { @Test @SuppressWarnings("unchecked") public void testResponseEventHandlerIsAdded() throws Exception { - Callback onResponseMock = mock(Callback.class); + BiConsumer onResponseMock = mock(BiConsumer.class); RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null); requester.onResponse(onResponseMock); @@ -131,7 +132,7 @@ public void testResponseEventHandlerIsAdded() throws Exception { @Test @SuppressWarnings("unchecked") public void testRawResponseEventHandlerIsAdded() throws Exception { - Callback onRawResponseMock = mock(Callback.class); + BiConsumer onRawResponseMock = mock(BiConsumer.class); RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null); requester.onRawResponse(onRawResponseMock); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java index 9ad08641..67ee94c2 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java @@ -1,19 +1,5 @@ package io.github.tcdl.msb.impl; -import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.Producer; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.Responder; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.config.MsbConfig; -import io.github.tcdl.msb.message.MessageFactory; -import io.github.tcdl.msb.support.TestUtils; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import java.time.Clock; - import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -25,6 +11,20 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.github.tcdl.msb.ChannelManager; +import io.github.tcdl.msb.Producer; +import io.github.tcdl.msb.api.MessageTemplate; +import io.github.tcdl.msb.api.Responder; +import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.config.MsbConfig; +import io.github.tcdl.msb.message.MessageFactory; +import io.github.tcdl.msb.support.TestUtils; + +import java.time.Clock; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; public class ResponderImplTest { @@ -58,14 +58,14 @@ public void setUp() { when(msbContextSpy.getMessageFactory()).thenReturn(spyMessageFactory); when(mockChannelManager.findOrCreateProducer(anyString())).thenReturn(mockProducer); - responder = new ResponderImpl(messageTemplate, originalMessage, msbContextSpy); + responder = new ResponderImpl(messageTemplate, originalMessage, null, msbContextSpy); } @Test public void testResponderConstructorOk() { MsbContextImpl context = TestUtils.createSimpleMsbContext(); Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - new ResponderImpl(messageTemplate, originalMessage, context); + new ResponderImpl(messageTemplate, originalMessage, null, context); } @Test @@ -101,7 +101,7 @@ public void testProducerPublishUseCorrectPayload() { @Test public void testProducerPublishWithTags() { String[] tags = new String[]{"tag1", "tag2"}; - responder = new ResponderImpl(TestUtils.createSimpleMessageTemplate(tags), originalMessage, msbContextSpy); + responder = new ResponderImpl(TestUtils.createSimpleMessageTemplate(tags), originalMessage, null, msbContextSpy); ArgumentCaptor argument = ArgumentCaptor.forClass(Message.class); responder.send(TestUtils.createSimpleRequestPayload()); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java index 2e7ffd18..343d6255 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java @@ -1,6 +1,16 @@ package io.github.tcdl.msb.impl; -import com.fasterxml.jackson.core.type.TypeReference; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.Producer; @@ -11,21 +21,12 @@ import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.support.TestUtils; + import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyObject; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; +import com.fasterxml.jackson.core.type.TypeReference; public class ResponderServerImplTest { @@ -62,7 +63,7 @@ public void testResponderServerProcessPayloadSuccess() throws Exception { verify(spyChannelManager).subscribe(anyString(), subscriberCaptor.capture()); Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - subscriberCaptor.getValue().handleMessage(originalMessage); + subscriberCaptor.getValue().handleMessage(originalMessage, null); verify(spyResponderServer).onResponder(anyObject()); } @@ -87,7 +88,7 @@ public void testResponderServerProcessUnexpectedPayload() throws Exception { // simulate incoming request ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(RestPayload.class); ResponderImpl responder = spy( - new ResponderImpl(messageTemplate, incomingMessage, msbContext)); + new ResponderImpl(messageTemplate, incomingMessage, null, msbContext)); responderServer.onResponder(responder); verify(responder).send(responseCaptor.capture()); assertEquals(ResponderServer.PAYLOAD_CONVERSION_ERROR_CODE, responseCaptor.getValue().getStatusCode().intValue()); @@ -109,7 +110,7 @@ public void testResponderServerProcessHandlerThrowException() throws Exception { // simulate incoming request ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(RestPayload.class); ResponderImpl responder = spy( - new ResponderImpl(messageTemplate, TestUtils.createMsbRequestMessageNoPayload(TOPIC), msbContext)); + new ResponderImpl(messageTemplate, TestUtils.createMsbRequestMessageNoPayload(TOPIC), null, msbContext)); responderServer.onResponder(responder); verify(responder).send(responseCaptor.capture()); @@ -133,7 +134,7 @@ public void testCreateResponderWithResponseTopic() { .create(TOPIC, messageTemplate, msbContext1, handler, new TypeReference() {}); Message incomingMessage = TestUtils.createMsbRequestMessageNoPayload(TOPIC); - Responder responder = responderServer.createResponder(incomingMessage); + Responder responder = responderServer.createResponder(incomingMessage, null); assertEquals(incomingMessage, responder.getOriginalMessage()); responder.sendAck(1, 1); @@ -157,7 +158,7 @@ public void testCreateResponderNoResponseTopic() { .create(TOPIC, messageTemplate, msbContext, handler, new TypeReference() {}); Message incomingMessage = TestUtils.createMsbBroadcastMessageNoPayload(TOPIC); - Responder responder = responderServer.createResponder(incomingMessage); + Responder responder = responderServer.createResponder(incomingMessage, null); assertEquals(incomingMessage, responder.getOriginalMessage()); responder.sendAck(1, 1); diff --git a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregatorTest.java b/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregatorTest.java index fe95239d..b25acef9 100644 --- a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregatorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregatorTest.java @@ -1,7 +1,15 @@ package io.github.tcdl.msb.monitor.aggregator; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; +import static io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator.DEFAULT_HEARTBEAT_INTERVAL_MS; +import static io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator.DEFAULT_HEARTBEAT_TIMEOUT_MS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.api.Callback; @@ -15,7 +23,6 @@ import io.github.tcdl.msb.monitor.agent.AgentTopicStats; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; -import org.junit.Test; import java.time.Instant; import java.util.Arrays; @@ -26,16 +33,10 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import static io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator.DEFAULT_HEARTBEAT_INTERVAL_MS; -import static io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator.DEFAULT_HEARTBEAT_TIMEOUT_MS; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; public class DefaultChannelMonitorAggregatorTest { @@ -59,7 +60,7 @@ public void testOnAnnounce() { String INSTANCE_ID = "instanceId"; Message announcementMessage = createAnnouncementMessageWith2Topics(INSTANCE_ID, TOPIC1, TOPIC2); - channelMonitor.onAnnounce(announcementMessage); + channelMonitor.onAnnounce(announcementMessage, null); assertEquals(1, channelMonitor.masterAggregatorStats.getServiceDetailsById().size()); assertTrue(channelMonitor.masterAggregatorStats.getServiceDetailsById().containsKey(INSTANCE_ID)); diff --git a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java b/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java index 3fa2b818..8b89bd17 100644 --- a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java +++ b/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java @@ -1,6 +1,12 @@ package io.github.tcdl.msb.monitor.aggregator; -import com.fasterxml.jackson.databind.JsonNode; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.ObjectFactory; import io.github.tcdl.msb.api.RequestOptions; @@ -10,19 +16,16 @@ import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; + +import java.util.List; +import java.util.function.BiConsumer; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; -import java.util.List; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import com.fasterxml.jackson.databind.JsonNode; public class HeartbeatTaskTest { @@ -37,7 +40,7 @@ public class HeartbeatTaskTest { public void setUp() { when(mockObjectFactory.createRequester(anyString(), any(RequestOptions.class))).thenReturn(mockRequester); @SuppressWarnings("unchecked") - Callback responseHandler = any(Callback.class); + BiConsumer responseHandler = any(BiConsumer.class); when(mockRequester.onRawResponse(responseHandler)).thenReturn(mockRequester); @SuppressWarnings("unchecked") Callback endHandler = any(Callback.class); @@ -49,7 +52,7 @@ public void setUp() { public void testRun() { heartbeatTask.run(); - ArgumentCaptor onResponseCaptor = ArgumentCaptor.forClass(Callback.class); + ArgumentCaptor onResponseCaptor = ArgumentCaptor.forClass(BiConsumer.class); ArgumentCaptor onEndCaptor = ArgumentCaptor.forClass(Callback.class); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(List.class); @@ -61,8 +64,8 @@ public void testRun() { // simulate incoming messages Message msg1 = TestUtils.createSimpleRequestMessage("from:responder"); Message msg2 = TestUtils.createSimpleRequestMessage("from:responder"); - onResponseCaptor.getValue().call(msg1); - onResponseCaptor.getValue().call(msg2); + onResponseCaptor.getValue().accept(msg1, null); + onResponseCaptor.getValue().accept(msg2, null); onEndCaptor.getValue().call(null); verify(mockMessageHandler).call(messageCaptor.capture()); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java b/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java index f4911380..534114c8 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java @@ -9,7 +9,6 @@ import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.examples.payload.Request; -import javax.script.ScriptException; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Collections; @@ -18,6 +17,8 @@ import java.util.Map; import java.util.UUID; +import javax.script.ScriptException; + /** * Microservice which is listening for incoming messages, creates requests to another microservices( * data-extractor, airport-extractor, resort-extractor), concatenates responses and returns result response @@ -75,7 +76,7 @@ public static void main(String[] args) throws ScriptException, FileNotFoundExcep final String[] result = {""}; List responses = Collections.synchronizedList(new ArrayList<>()); - requester.onResponse(responses::add) + requester.onResponse((message, ackHandler) -> {responses.add(message);}) .onEnd(end -> { for (RestPayload payload : responses) { System.out.println(">>> MESSAGE: " + payload); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/PingService.java b/examples/src/main/java/io/github/tcdl/msb/examples/PingService.java index 36e33d14..feaa9ae7 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/PingService.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/PingService.java @@ -6,11 +6,12 @@ import io.github.tcdl.msb.api.ObjectFactory; import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Requester; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class PingService { private static final Logger LOG = LoggerFactory.getLogger(PingService.class); @@ -29,7 +30,7 @@ public static void main(String[] args) { ObjectFactory objectFactory = msbContext.getObjectFactory(); Requester requester = objectFactory.createRequester("pingpong:namespace", requestOptions, String.class) - .onResponse(payload -> LOG.info(String.format("Received response '%s'", payload))) // Handling the one response + .onResponse((payload, ackHandler) -> LOG.info(String.format("Received response '%s'", payload))) // Handling the one response .onEnd(arg -> LOG.info("Received all expected responses")); // Handling all response arrival or timeout requester.publish("PING", UUID.randomUUID().toString()); // Send the message From c361634896908093364f1965e7dac18746c3e8b2 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Fri, 4 Dec 2015 07:24:47 +0200 Subject: [PATCH 005/226] CLI minor change --- cli/src/main/resources/application.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/src/main/resources/application.conf b/cli/src/main/resources/application.conf index 829cf2b2..dcc41c71 100644 --- a/cli/src/main/resources/application.conf +++ b/cli/src/main/resources/application.conf @@ -22,6 +22,7 @@ msbConfig { # -1 means unlimited consumerThreadPoolQueueCapacity = 20 requeueRejectedMessages = true + prefetchCount = 0 } } From 7337d463dfa79e4a72a0406cdfd588f66fb3b6c9 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Fri, 4 Dec 2015 11:11:52 +0200 Subject: [PATCH 006/226] Implemented proposals from comments. --- .../msb/adapters/amqp/AmqpAcknowledgementHandler.java | 2 +- .../main/java/io/github/tcdl/msb/api/Responder.java | 7 ++++++- .../io/github/tcdl/msb/impl/NoopResponderImpl.java | 9 +++------ .../java/io/github/tcdl/msb/impl/ResponderImpl.java | 10 +++------- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java index 5368d40e..9883258e 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java @@ -56,7 +56,7 @@ public void rejectMessage() { if (!isAcknowledgementSent) { try { channel.basicReject(deliveryTag, isRequeueRejectedMessages); - LOG.error(String.format("[consumer tag: %s] AMQP reject has been sent for message: %s", consumerTag, bodyStr)); + LOG.debug(String.format("[consumer tag: %s] AMQP reject has been sent for message: %s", consumerTag, bodyStr)); isAcknowledgementSent = true; } catch (Exception e) { LOG.error(String.format("[consumer tag: %s] Got exception:", consumerTag), e); diff --git a/core/src/main/java/io/github/tcdl/msb/api/Responder.java b/core/src/main/java/io/github/tcdl/msb/api/Responder.java index f337b912..748b47de 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/Responder.java +++ b/core/src/main/java/io/github/tcdl/msb/api/Responder.java @@ -6,7 +6,7 @@ /** * Responsible for creating responses and acknowledgements and sending them to the bus. */ -public interface Responder extends ConsumerAdapter.AcknowledgementHandler { +public interface Responder { /** * Send acknowledge message. @@ -27,4 +27,9 @@ public interface Responder extends ConsumerAdapter.AcknowledgementHandler { * @return original message to send a response to */ Message getOriginalMessage(); + + /** + * @return AcknowledgementHandler for explicit confirm/reject incoming messages + */ + ConsumerAdapter.AcknowledgementHandler getAcknowledgementHandler(); } \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java index 355be9d7..dcc34b49 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java @@ -1,6 +1,7 @@ package io.github.tcdl.msb.impl; import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.adapters.ConsumerAdapter.AcknowledgementHandler; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Message; @@ -40,13 +41,9 @@ public Message getOriginalMessage() { } @Override - public void confirmMessage() { - ackHandler.confirmMessage(); + public AcknowledgementHandler getAcknowledgementHandler() { + return ackHandler; } - @Override - public void rejectMessage() { - ackHandler.rejectMessage(); - } } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java index f196053b..15ff8862 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java @@ -3,6 +3,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.Producer; import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.adapters.ConsumerAdapter.AcknowledgementHandler; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Acknowledge.Builder; @@ -80,13 +81,8 @@ public Message getOriginalMessage() { } @Override - public void confirmMessage() { - ackHandler.confirmMessage(); + public AcknowledgementHandler getAcknowledgementHandler() { + return this.ackHandler; } - @Override - public void rejectMessage() { - ackHandler.rejectMessage(); - } - } \ No newline at end of file From a863d3d5ea0c6c2471a22c93dbf33fe8b91f1774 Mon Sep 17 00:00:00 2001 From: anha1 Date: Fri, 4 Dec 2015 13:37:04 +0200 Subject: [PATCH 007/226] WEB-16081 amqp ack - unit testing and misc. minor fixes --- .../amqp/AmqpAcknowledgementHandler.java | 10 +- .../adapters/amqp/AmqpMessageConsumer.java | 24 +-- .../amqp/AmqpAcknowledgementHandlerTest.java | 146 ++++++++++++++++++ .../amqp/AmqpMessageConsumerTest.java | 56 +++++-- .../amqp/AmqpMessageProcessingTaskTest.java | 26 ++-- 5 files changed, 219 insertions(+), 43 deletions(-) create mode 100644 amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java index 9883258e..3dfeb1c1 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java @@ -20,18 +20,16 @@ public class AmqpAcknowledgementHandler implements AcknowledgementHandler { final Channel channel; final String consumerTag; - final String bodyStr; final long deliveryTag; final boolean isRequeueRejectedMessages; boolean isAcknowledgementSent = false; - public AmqpAcknowledgementHandler(Channel channel, String consumerTag, String bodyStr, long deliveryTag, + public AmqpAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) { super(); this.channel = channel; this.consumerTag = consumerTag; - this.bodyStr = bodyStr; this.deliveryTag = deliveryTag; this.isRequeueRejectedMessages = isRequeueRejectedMessages; } @@ -41,10 +39,9 @@ public void confirmMessage() { if (!isAcknowledgementSent) { try { channel.basicAck(deliveryTag, false); - LOG.debug(String.format("[consumer tag: %s] AMQP ack has been sent for message '%s'", consumerTag, bodyStr)); isAcknowledgementSent = true; } catch (Exception e) { - LOG.error(String.format("[consumer tag: %s] Got exception:", consumerTag), e); + LOG.error(String.format("[consumer tag: %s] Got exception when trying to confirm a message:", consumerTag), e); } } else { LOG.warn(ACK_WAS_ALREADY_SENT); @@ -56,10 +53,9 @@ public void rejectMessage() { if (!isAcknowledgementSent) { try { channel.basicReject(deliveryTag, isRequeueRejectedMessages); - LOG.debug(String.format("[consumer tag: %s] AMQP reject has been sent for message: %s", consumerTag, bodyStr)); isAcknowledgementSent = true; } catch (Exception e) { - LOG.error(String.format("[consumer tag: %s] Got exception:", consumerTag), e); + LOG.error(String.format("[consumer tag: %s] Got exception when trying to reject a message:", consumerTag), e); } } else { LOG.warn(ACK_WAS_ALREADY_SENT); diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java index f9a4c34a..19ed8377 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java @@ -43,34 +43,34 @@ public AmqpMessageConsumer(Channel channel, ExecutorService consumerThreadPool, @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + long deliveryTag = envelope.getDeliveryTag(); + AmqpAcknowledgementHandler ackHandler = createAcknowledgementHandler( + getChannel(), consumerTag, deliveryTag, amqpBrokerConfig.isRequeueRejectedMessages()); try { Charset charset = amqpBrokerConfig.getCharset(); - boolean requeueRejectedMessages = amqpBrokerConfig.isRequeueRejectedMessages(); + String bodyStr = new String(body, charset); - long deliveryTag = envelope.getDeliveryTag(); - + LOG.debug(String.format("[consumer tag: %s] Message consumed from broker: %s", consumerTag, bodyStr)); - AmqpAcknowledgementHandler ackHandler = createAcknowledgementHandler(getChannel(), - consumerTag, bodyStr, deliveryTag, requeueRejectedMessages); try { consumerThreadPool.submit(new AmqpMessageProcessingTask(consumerTag, bodyStr, msgHandler, ackHandler)); - LOG.debug(String.format("[consumer tag: %s] Message has been put in the processing queue: %s. About to send AMQP ack...", + LOG.debug(String.format("[consumer tag: %s] Message has been put in the processing queue: %s.", consumerTag, bodyStr)); } catch (Exception e) { - LOG.error(String.format("[consumer tag: %s] Couldn't put message in the processing queue: %s. About to send AMQP reject...", + LOG.error(String.format("[consumer tag: %s] Couldn't put message in the processing queue: %s.", consumerTag, bodyStr), e); - ackHandler.autoReject(); + throw e; } } catch (Exception e) { // Catch all exceptions to prevent AMQP channel to be closed - LOG.error(String.format("[consumer tag: %s] Got exception while processing incoming message", consumerTag), e); + LOG.error(String.format("[consumer tag: %s] Got exception while processing incoming message. About to send AMQP reject...", consumerTag), e); + ackHandler.autoReject(); } } - AmqpAcknowledgementHandler createAcknowledgementHandler(Channel channel, String consumerTag, - String bodyStr, long deliveryTag, boolean isRequeueRejectedMessages) { - return new AmqpAcknowledgementHandler(channel, consumerTag, bodyStr, deliveryTag, isRequeueRejectedMessages); + AmqpAcknowledgementHandler createAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) { + return new AmqpAcknowledgementHandler(channel, consumerTag, deliveryTag, isRequeueRejectedMessages); } } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java new file mode 100644 index 00000000..a53c8962 --- /dev/null +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java @@ -0,0 +1,146 @@ +package io.github.tcdl.msb.adapters.amqp; + +import com.rabbitmq.client.Channel; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.stream.IntStream; + +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class AmqpAcknowledgementHandlerTest { + + private AmqpAcknowledgementHandler handler; + + @Mock + private Channel mockChannel; + + private long deliveryTag = 123123123; + + private boolean isRequeueByDefault = true; + + @Before + public void setUp() { + handler = getHandler(isRequeueByDefault); + } + + @Test + public void testMessageConfirmed() throws Exception { + handler.confirmMessage(); + verifyConfirmedOnce(); + } + + @Test + public void testMessageRejectedWithRequeue() throws Exception { + handler.rejectMessage(); + verifyRejectedOnce(true); + } + + @Test + public void testMessageRejectedWithoutRequeue() throws Exception { + handler = getHandler(false); + handler.rejectMessage(); + verifyRejectedOnce(false); + } + + @Test + public void testOnlyFirstRejectInvoked() throws Exception { + handler.rejectMessage(); + verifyRejectedOnce(); + submitMultipleConfirmRejectRequests(); + verifyRejectedOnce(); + } + + @Test + public void testOnlyFirstConfirmInvoked() throws Exception { + handler.confirmMessage(); + verifyConfirmedOnce(); + submitMultipleConfirmRejectRequests(); + verifyConfirmedOnce(); + } + + @Test + public void testAutoConfirmConfirmsMessageOnce() throws Exception { + handler.autoConfirm(); + verifyConfirmedOnce(); + submitMultipleAutoConfirmAutoRejectRequests(); + verifyConfirmedOnce(); + } + + @Test + public void testAutoRejectRejectsMessageOnce() throws Exception { + handler.autoReject(); + verifyRejectedOnce(); + submitMultipleAutoConfirmAutoRejectRequests(); + verifyRejectedOnce(); + } + + @Test + public void testAutoConfirmIgnoredWhenConfirmedByClient() throws Exception { + handler.confirmMessage(); + verifyConfirmedOnce(); + handler.autoConfirm(); + verifyConfirmedOnce(); + } + + @Test + public void testAutoRejectIgnoredWhenConfirmedByClient() throws Exception { + handler.confirmMessage(); + verifyConfirmedOnce(); + handler.autoReject(); + verifyConfirmedOnce(); + } + + @Test + public void testAutoConfirmIgnoredWhenRejectedByClient() throws Exception { + handler.rejectMessage(); + verifyRejectedOnce(); + handler.autoConfirm(); + verifyRejectedOnce(); + } + + @Test + public void testAutoRejectIgnoredWhenRejectedByClient() throws Exception { + handler.rejectMessage(); + verifyRejectedOnce(); + handler.autoReject(); + verifyRejectedOnce(); + } + + private void verifyConfirmedOnce() throws Exception { + verify(mockChannel, times(1)).basicAck(deliveryTag, false); + verifyNoMoreInteractions(mockChannel); + } + + private void verifyRejectedOnce() throws Exception { + verifyRejectedOnce(isRequeueByDefault); + } + + private void verifyRejectedOnce(boolean isRequeue) throws Exception { + verify(mockChannel, times(1)).basicReject(deliveryTag, isRequeue); + verifyNoMoreInteractions(mockChannel); + } + + private void submitMultipleConfirmRejectRequests() { + IntStream.range(0, 5).forEach((i) -> { + handler.confirmMessage(); + handler.rejectMessage(); + }); + } + + private void submitMultipleAutoConfirmAutoRejectRequests() { + IntStream.range(0, 5).forEach((i) -> { + handler.autoReject(); + handler.autoConfirm(); + }); + } + + private AmqpAcknowledgementHandler getHandler(boolean isRequeue) { + return new AmqpAcknowledgementHandler(mockChannel, "any", deliveryTag, isRequeue); + } + +} diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java index 14444165..fcf0b560 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java @@ -5,10 +5,8 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; + import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; @@ -19,33 +17,44 @@ import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Envelope; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +@RunWith(MockitoJUnitRunner.class) public class AmqpMessageConsumerTest { - private static final boolean REQUEUE_REJECTED_MESSAGES = true; - + @Mock private Channel mockChannel; + + @Mock private ExecutorService mockExecutorService; + + @Mock private ConsumerAdapter.RawMessageHandler mockMessageHandler; + + @Mock private AmqpBrokerConfig mockBrokerConfig; + @Mock + private AmqpAcknowledgementHandler amqpAcknowledgementHandler; + private AmqpMessageConsumer amqpMessageConsumer; @Before public void setUp() { - mockChannel = mock(Channel.class); - mockExecutorService = mock(ExecutorService.class); - mockMessageHandler = mock(ConsumerAdapter.RawMessageHandler.class); - mockBrokerConfig = mock(AmqpBrokerConfig.class); - when(mockBrokerConfig.getCharset()).thenReturn(Charset.forName("UTF-8")); - when(mockBrokerConfig.isRequeueRejectedMessages()).thenReturn(REQUEUE_REJECTED_MESSAGES); - amqpMessageConsumer = new AmqpMessageConsumer(mockChannel, mockExecutorService, mockMessageHandler, mockBrokerConfig); + amqpMessageConsumer = new AmqpMessageConsumer(mockChannel, mockExecutorService, mockMessageHandler, mockBrokerConfig) { + @Override + AmqpAcknowledgementHandler createAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) { + return amqpAcknowledgementHandler; + } + }; } @Test @@ -80,7 +89,24 @@ public void testMessageCannotBeSubmittedForProcessing() throws IOException { try { amqpMessageConsumer.handleDelivery("consumer tag", envelope, null, "some message".getBytes()); - verify(mockChannel).basicReject(deliveryTag, REQUEUE_REJECTED_MESSAGES); + verify(amqpAcknowledgementHandler, times(1)).autoReject(); + } catch (Exception e) { + fail(); + } + } + + @Test + public void testMessageCannotBeProcessedBeforeSubmit() throws IOException { + long deliveryTag = 1234L; + Envelope envelope = mock(Envelope.class); + when(envelope.getDeliveryTag()).thenReturn(deliveryTag); + + when(mockBrokerConfig.getCharset()).thenThrow( + new RuntimeException("Something really unexpected happened even before task submit attempt")); + + try { + amqpMessageConsumer.handleDelivery("consumer tag", envelope, null, "some message".getBytes()); + verify(amqpAcknowledgementHandler, times(1)).autoReject(); } catch (Exception e) { fail(); } @@ -97,7 +123,7 @@ public void testRejectFailed() throws IOException { try { amqpMessageConsumer.handleDelivery("consumer tag", envelope, null, "some message".getBytes()); - verify(mockChannel).basicReject(eq(deliveryTag), anyBoolean()); + verify(amqpAcknowledgementHandler, times(1)).autoReject(); } catch (Exception e) { fail(); } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java index 98ac7cbb..82c46f86 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java @@ -3,10 +3,8 @@ import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; + import io.github.tcdl.msb.adapters.ConsumerAdapter; import java.io.IOException; @@ -15,30 +13,37 @@ import org.junit.Test; import com.rabbitmq.client.Channel; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +@RunWith(MockitoJUnitRunner.class) public class AmqpMessageProcessingTaskTest { private String messageStr = "some message"; + @Mock private Channel mockChannel; + + @Mock private ConsumerAdapter.RawMessageHandler mockMessageHandler; + + @Mock private AmqpAcknowledgementHandler mockAcknowledgementHandler; - + private AmqpMessageProcessingTask task; @Before public void setUp() { - mockChannel = mock(Channel.class); - mockMessageHandler = mock(ConsumerAdapter.RawMessageHandler.class); - mockAcknowledgementHandler = mock(AmqpAcknowledgementHandler.class); task = new AmqpMessageProcessingTask("consumer tag", messageStr, mockMessageHandler, mockAcknowledgementHandler); } @Test public void testMessageProcessing() throws IOException { task.run(); - verify(mockMessageHandler).onMessage(messageStr, mockAcknowledgementHandler); + verify(mockAcknowledgementHandler, times(1)).autoConfirm(); + verifyNoMoreInteractions(mockAcknowledgementHandler); } @Test @@ -49,6 +54,9 @@ public void testExceptionDuringProcessing() { task.run(); // Verify that AMQP ack has not been sent verifyNoMoreInteractions(mockChannel); + + verify(mockAcknowledgementHandler, times(1)).autoReject(); + verifyNoMoreInteractions(mockAcknowledgementHandler); } catch (Exception e) { fail(); } From 642a0e77efb9cbe02bc8df0e3971e0010a457f1e Mon Sep 17 00:00:00 2001 From: anha1 Date: Fri, 4 Dec 2015 16:18:45 +0200 Subject: [PATCH 008/226] WEB-16081 amqp ack - integration testing; unnecessary long testing timeouts fixed. --- .../bdd/steps/RequesterResponderSteps.java | 63 ++++++++++++++++--- .../scenarios/requester_responder.story | 29 ++++++++- 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java index c0518f5a..dad161a3 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java @@ -13,6 +13,10 @@ import org.junit.Assert; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME; @@ -24,6 +28,10 @@ public class RequesterResponderSteps extends MsbSteps { private Requester requester; private String responseBody; private Map receivedResponse; + private CompletableFuture> receivedResponseFuture; + private int countRequestsReceived = 0; + private Optional nextRequestAckType = Optional.empty(); + // responder steps @Given("responder server listens on namespace $namespace") @@ -35,16 +43,40 @@ public void createResponderServer(String namespace) { @When("responder server from $contextName listens on namespace $namespace") public void createResponderServer(String contextName, String namespace) { ObjectMapper mapper = helper.getPayloadMapper(contextName); + countRequestsReceived = 0; helper.createResponderServer(contextName, namespace, (request, responder) -> { if (responseBody != null) { - RestPayload payload = new RestPayload.Builder() - .withBody(Utils.fromJson(responseBody, Map.class, mapper)) - .build(); - responder.send(payload); + countRequestsReceived++; + boolean isSendResponse = true; + + switch (nextRequestAckType.orElseGet(()->"auto")) { + case "confirm": + responder.getAcknowledgementHandler().confirmMessage(); + break; + case "reject": + responder.getAcknowledgementHandler().rejectMessage(); + isSendResponse = false; + break; + } + + nextRequestAckType = Optional.empty(); + + if(isSendResponse) { + RestPayload payload = new RestPayload.Builder() + .withBody(Utils.fromJson(responseBody, Map.class, mapper)) + .build(); + responder.send(payload); + } } }).listen(); } + @Given("responder server will $nextRequestAckType next request") + public void setNextRequestAckType(String nextRequestAckType) throws Exception { + this.nextRequestAckType = Optional.of(nextRequestAckType); + + } + @Given("responder server responds with '$body'") @When("responder server responds with '$body'") public void respond(String body) { @@ -69,30 +101,42 @@ public void sendRequest() throws Exception { @When("requester from $contextName sends a request") public void sendRequest(String contextName) throws Exception { + onBeforeRequest(); RestPayload payload = helper.createFacetParserPayload("QUERY", null); helper.sendRequest(requester, payload, 1, this::onResponse); } @When("requester sends a request with query '$query'") public void sendRequestWithQuery(String query) throws Exception { + onBeforeRequest(); RestPayload payload = helper.createFacetParserPayload(query, null); helper.sendRequest(requester, payload, true, 1, null, this::onResponse); } @When("requester sends a request with body '$body'") public void sendRequestWithBody(String body) throws Exception { + onBeforeRequest(); RestPayload payload = helper.createFacetParserPayload(null, body); helper.sendRequest(requester, payload, true, 1, null, this::onResponse); } + private void onBeforeRequest() { + receivedResponse = null; + receivedResponseFuture = new CompletableFuture<>(); + } + private void onResponse(RestPayload> payload) { - receivedResponse = payload.getBody(); + receivedResponseFuture.complete(payload.getBody()); } @Then("requester gets response in $timeout ms") public void waitForResponse(long timeout) throws Exception { - Thread.sleep(timeout); - Assert.assertNotNull("Response has not been received", receivedResponse); + try { + receivedResponse = receivedResponseFuture.get(timeout, TimeUnit.MILLISECONDS); + } catch (TimeoutException timeoutException) { + Assert.fail("Response has not been received during a timeout"); + } + Assert.assertNotNull("Response received is null", receivedResponse); } @Then("response equals $table") @@ -107,6 +151,11 @@ public void responseEquals(ExamplesTable table) throws Exception { outcomes.verify(); } + @Then("responder requests received count equals $expectedRequestsReceivedCount") + public void requestCountEquals(int expectedCountRequestsReceived) throws Exception { + Assert.assertEquals(expectedCountRequestsReceived, countRequestsReceived); + } + @Then("response contains $table") public void responseContains(ExamplesTable table) throws Exception { Map expected = table.getRow(0); diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story index f539a059..c701d3a6 100644 --- a/acceptance/src/test/resources/scenarios/requester_responder.story +++ b/acceptance/src/test/resources/scenarios/requester_responder.story @@ -1,7 +1,6 @@ Lifecycle: Before: Given start MSB -And responder server listens on namespace test:jbehave After: Outcome: ANY Then shutdown MSB @@ -9,11 +8,39 @@ Then shutdown MSB Scenario: Sends a request to a responder server and waits for response Given responder server responds with '{"result": "hello jbehave"}' +And responder server listens on namespace test:jbehave And requester sends requests to namespace test:jbehave When requester sends a request Then requester gets response in 5000 ms +And responder requests received count equals 1 And response equals |result| |hello jbehave| +Scenario: Responder confirms a message manually + +Given responder server responds with '{"result": "hello jbehave - manual confirm"}' +And responder server listens on namespace test:jbehave +And responder server will confirm next request +And requester sends requests to namespace test:jbehave +When requester sends a request +Then requester gets response in 5000 ms +And responder requests received count equals 1 +And response equals +|result| +|hello jbehave - manual confirm| + +Scenario: Responder rejects a first message delivery so it will be redelivered + +Given responder server responds with '{"result": "hello jbehave - manual reject"}' +And responder server listens on namespace test:jbehave +And responder server will reject next request +And requester sends requests to namespace test:jbehave +When requester sends a request +Then requester gets response in 5000 ms +And responder requests received count equals 2 +And response equals +|result| +|hello jbehave - manual reject| + From 3011fe32e4bdf624a7dafe34c8dbd26bcb861d71 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Fri, 4 Dec 2015 18:23:51 +0200 Subject: [PATCH 009/226] Bumped 1.4.0 version --- acceptance/pom.xml | 2 +- amqp/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index fdcc302e..41b7b1f6 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/amqp/pom.xml b/amqp/pom.xml index 9000cb20..3d0e1bb3 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/cli/pom.xml b/cli/pom.xml index 221ca44d..ea27499f 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index 2761c876..c271380e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index eff3d6b0..886d8136 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index b77b5f98..1c485ef2 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT msb java msb java pom From aafc50c1300ffe2ec429ce1ee7a4b38e606b5bfa Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Fri, 4 Dec 2015 18:59:12 +0200 Subject: [PATCH 010/226] Updated msb.md --- doc/MSB.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/MSB.md b/doc/MSB.md index a7d6c3ac..6626b09c 100644 --- a/doc/MSB.md +++ b/doc/MSB.md @@ -14,6 +14,12 @@ The diagram below shows one possible approach when microservices communicate thr MSB = MicroService Bus. +MSb-Java version number MAJOR.MINOR.PATCH, is incremented when: + +MAJOR version - MSB compatible protocol was changed +MINOR version - incompatible API changes was made, +PATCH version - added functionality in a backwards-compatible manner, or backwards-compatible bug fixes was made. + # Supported microservice models Below we consider different ways in which microservices can interact with each other using MSB-Java. From 7ebc28495aea55b948648c4a8f41f69a9bba4ecd Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Sat, 5 Dec 2015 21:35:56 +0200 Subject: [PATCH 011/226] Added release-notes --- release-notes.html | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 release-notes.html diff --git a/release-notes.html b/release-notes.html new file mode 100644 index 00000000..b30199ad --- /dev/null +++ b/release-notes.html @@ -0,0 +1,24 @@ + + + + Welcome to MSB-Java version 1.3.0 + + + +

Welcome to MSB-Java version 1.3.0

+ +

December 4, 2015

+ +
MSB-Java version 1.3.0 is a maintenance release that fixes some minor bags.
+It is also adds two new features.
+
+------------------------------------------------------------------------------------
+
+Features of MSB-Java version 1.3.0:
+   - Removed REST-style payload constraint on parameters type 
+   - Updated Requester behavior. It waits for acks even if configured to 
+     `waitForResponses: 0`
+   
+    
+ + From be8b068eafba8b64b61c4f8f6aaea1838f436565 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Sun, 6 Dec 2015 00:24:24 +0200 Subject: [PATCH 012/226] [maven-release-plugin] prepare release msb-java-1.3.0 --- acceptance/pom.xml | 4 ++-- amqp/pom.xml | 4 ++-- cli/pom.xml | 4 ++-- core/pom.xml | 4 ++-- examples/pom.xml | 4 ++-- pom.xml | 8 ++++++-- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index fdcc302e..2799f52a 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.3.0 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.3.0 tcdl diff --git a/amqp/pom.xml b/amqp/pom.xml index 9000cb20..58eefcd6 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.3.0 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.3.0 tcdl diff --git a/cli/pom.xml b/cli/pom.xml index 221ca44d..df278e5b 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.3.0 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.3.0 tcdl diff --git a/core/pom.xml b/core/pom.xml index 2761c876..35d65e4c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.3.0 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.3.0 tcdl diff --git a/examples/pom.xml b/examples/pom.xml index eff3d6b0..f8758491 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.3.0 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.3.0 tcdl diff --git a/pom.xml b/pom.xml index b77b5f98..bceda861 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.3.0-SNAPSHOT + 1.3.0 msb java msb java pom @@ -11,7 +11,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.3.0 @@ -179,6 +179,10 @@ false release true + + pom.xml + settings.xml + From b560aca8baa321da8d20726bd9af61c2dbd7869a Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Sun, 6 Dec 2015 00:24:57 +0200 Subject: [PATCH 013/226] [maven-release-plugin] prepare for next development iteration --- acceptance/pom.xml | 4 ++-- amqp/pom.xml | 4 ++-- cli/pom.xml | 4 ++-- core/pom.xml | 4 ++-- examples/pom.xml | 4 ++-- pom.xml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 2799f52a..41b7b1f6 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0 + 1.4.0-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.3.0 + HEAD tcdl diff --git a/amqp/pom.xml b/amqp/pom.xml index 58eefcd6..3d0e1bb3 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0 + 1.4.0-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.3.0 + HEAD tcdl diff --git a/cli/pom.xml b/cli/pom.xml index df278e5b..ea27499f 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0 + 1.4.0-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.3.0 + HEAD tcdl diff --git a/core/pom.xml b/core/pom.xml index 35d65e4c..c271380e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0 + 1.4.0-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.3.0 + HEAD tcdl diff --git a/examples/pom.xml b/examples/pom.xml index f8758491..886d8136 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.3.0 + 1.4.0-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.3.0 + HEAD tcdl diff --git a/pom.xml b/pom.xml index bceda861..d66b883d 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.3.0 + 1.4.0-SNAPSHOT msb java msb java pom @@ -11,7 +11,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.3.0 + HEAD From e8bdb14a4950de82fcbca678730d2c6436ef6da4 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Sun, 6 Dec 2015 14:12:37 +0200 Subject: [PATCH 014/226] AcknowledgementHandler removed to msb.api package --- .../amqp/AmqpAcknowledgementHandler.java | 2 +- .../tcdl/msb/cli/CliMessageHandler.java | 2 +- .../java/io/github/tcdl/msb/Consumer.java | 3 +- .../io/github/tcdl/msb/MessageHandler.java | 4 +- .../tcdl/msb/adapters/ConsumerAdapter.java | 20 ++------ .../tcdl/msb/api/AcknowledgementHandler.java | 21 +++++++++ .../io/github/tcdl/msb/api/Requester.java | 7 ++- .../io/github/tcdl/msb/api/Responder.java | 3 +- .../github/tcdl/msb/collector/Collector.java | 10 ++-- .../tcdl/msb/collector/CollectorManager.java | 4 +- .../github/tcdl/msb/events/EventHandlers.java | 20 ++++---- .../tcdl/msb/impl/NoopResponderImpl.java | 7 ++- .../github/tcdl/msb/impl/RequesterImpl.java | 8 ++-- .../github/tcdl/msb/impl/ResponderImpl.java | 7 ++- .../tcdl/msb/impl/ResponderServerImpl.java | 4 +- .../DefaultChannelMonitorAggregator.java | 4 +- .../java/io/github/tcdl/msb/ConsumerTest.java | 5 +- .../tcdl/msb/collector/CollectorTest.java | 46 +++++++++---------- .../monitor/aggregator/HeartbeatTaskTest.java | 4 +- 19 files changed, 93 insertions(+), 88 deletions(-) create mode 100644 core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java index 3dfeb1c1..7ce1e27c 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java @@ -1,6 +1,6 @@ package io.github.tcdl.msb.adapters.amqp; -import io.github.tcdl.msb.adapters.ConsumerAdapter.AcknowledgementHandler; +import io.github.tcdl.msb.api.AcknowledgementHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java index b7b00668..8d7bae60 100644 --- a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java +++ b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java @@ -1,7 +1,7 @@ package io.github.tcdl.msb.cli; import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.adapters.ConsumerAdapter.AcknowledgementHandler; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.exception.JsonConversionException; import java.io.IOException; diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index 6c7bc921..7bb85a32 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -1,6 +1,7 @@ package io.github.tcdl.msb; import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.exception.JsonSchemaValidationException; import io.github.tcdl.msb.api.message.Message; @@ -84,7 +85,7 @@ public void end() { * * @param jsonMessage message to process */ - protected void handleRawMessage(String jsonMessage, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { + protected void handleRawMessage(String jsonMessage, AcknowledgementHandler acknowledgeHandler) { LOG.debug("Topic [{}] message received [{}]", this.topic, jsonMessage); channelMonitorAgent.consumerMessageReceived(topic); diff --git a/core/src/main/java/io/github/tcdl/msb/MessageHandler.java b/core/src/main/java/io/github/tcdl/msb/MessageHandler.java index 1e664a06..6e8ab31f 100644 --- a/core/src/main/java/io/github/tcdl/msb/MessageHandler.java +++ b/core/src/main/java/io/github/tcdl/msb/MessageHandler.java @@ -1,6 +1,6 @@ package io.github.tcdl.msb; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.message.Message; public interface MessageHandler { @@ -11,5 +11,5 @@ public interface MessageHandler { * @param message the message content * @param acknowledgeHandler confirm/reject message handler */ - void handleMessage(Message message, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler); + void handleMessage(Message message, AcknowledgementHandler acknowledgeHandler); } diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java index 48fe5320..440747fb 100644 --- a/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java +++ b/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java @@ -1,5 +1,6 @@ package io.github.tcdl.msb.adapters; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.exception.ChannelException; /** @@ -30,24 +31,9 @@ interface RawMessageHandler { * Is called once a message arrives on the topic. * * @param jsonMessage incomming JSON message + * @param acknowledgementHandler confirm/reject message handler */ - void onMessage(String jsonMessage, AcknowledgementHandler handler); + void onMessage(String jsonMessage, AcknowledgementHandler acknowledgementHandler); } - /** - * Callback interface for a message acknowledgement - * - */ - interface AcknowledgementHandler { - /** - * Server should consider messages acknowledged once delivered - */ - void confirmMessage(); - - /** - * Reject a message - */ - void rejectMessage(); - - } } diff --git a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java new file mode 100644 index 00000000..453b5b87 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java @@ -0,0 +1,21 @@ +package io.github.tcdl.msb.api; + +/** + * Callback interface for explicit message acknowledgement + * + */ +public interface AcknowledgementHandler { + /** + * Inform server that a message was confirmed by consumer. + * Server should consider messages acknowledged once delivered + */ + void confirmMessage(); + + /** + * Inform server that a message was reject by consumer. + * AMQP Server may requeue message or delete it from queue depending on the + * requeueRejectedMessages configuration option + */ + void rejectMessage(); + +} diff --git a/core/src/main/java/io/github/tcdl/msb/api/Requester.java b/core/src/main/java/io/github/tcdl/msb/api/Requester.java index 56f931fe..fe4bcdaa 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/Requester.java +++ b/core/src/main/java/io/github/tcdl/msb/api/Requester.java @@ -1,6 +1,5 @@ package io.github.tcdl.msb.api; -import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.exception.ChannelException; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Acknowledge; @@ -69,7 +68,7 @@ public interface Requester { * @param acknowledgeHandler callback to be called * @return requester */ - Requester onAcknowledge(BiConsumer acknowledgeHandler); + Requester onAcknowledge(BiConsumer acknowledgeHandler); /** * Registers a callback to be called when response {@link Message} with payload part set of type {@literal T} is received. @@ -78,7 +77,7 @@ public interface Requester { * @return requester * @throws JsonConversionException if unable to convert payload to type {@literal T} */ - Requester onResponse(BiConsumer responseHandler); + Requester onResponse(BiConsumer responseHandler); /** * Registers a callback to be called when response {@link Message} with payload part set of is received. @@ -86,7 +85,7 @@ public interface Requester { * @param responseHandler callback to be called * @return requester */ - Requester onRawResponse(BiConsumer responseHandler); + Requester onRawResponse(BiConsumer responseHandler); /** * Registers a callback to be called when all expected responses for request message are processes or awaiting timeout for responses occurred. diff --git a/core/src/main/java/io/github/tcdl/msb/api/Responder.java b/core/src/main/java/io/github/tcdl/msb/api/Responder.java index 748b47de..d4802501 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/Responder.java +++ b/core/src/main/java/io/github/tcdl/msb/api/Responder.java @@ -1,6 +1,5 @@ package io.github.tcdl.msb.api; -import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.message.Message; /** @@ -31,5 +30,5 @@ public interface Responder { /** * @return AcknowledgementHandler for explicit confirm/reject incoming messages */ - ConsumerAdapter.AcknowledgementHandler getAcknowledgementHandler(); + AcknowledgementHandler getAcknowledgementHandler(); } \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index 889afacc..cc635700 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -2,7 +2,7 @@ import static io.github.tcdl.msb.support.Utils.ifNull; import static java.lang.Math.toIntExact; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.message.Acknowledge; @@ -59,9 +59,9 @@ public class Collector { private Clock clock; private Message requestMessage; - private Optional> onRawResponse = Optional.empty(); - private Optional> onResponse = Optional.empty(); - private Optional> onAcknowledge = Optional.empty(); + private Optional> onRawResponse = Optional.empty(); + private Optional> onResponse = Optional.empty(); + private Optional> onAcknowledge = Optional.empty(); private Optional> onEnd = Optional.empty(); private ScheduledFuture ackTimeoutFuture; @@ -122,7 +122,7 @@ public void listenForResponses() { collectorManager.registerCollector(this); } - public void handleMessage(Message incomingMessage, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { + public void handleMessage(Message incomingMessage, AcknowledgementHandler acknowledgeHandler) { LOG.debug("Received {}", incomingMessage); JsonNode rawPayload = incomingMessage.getRawPayload(); diff --git a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java index 3ce70047..e71e53d5 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java @@ -2,7 +2,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.MessageHandler; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException; import io.github.tcdl.msb.api.message.Message; @@ -34,7 +34,7 @@ public CollectorManager(String topic, ChannelManager channelManager) { * Determines correlationId from the incoming message and invokes the relevant {@link Collector} instance. */ @Override - public void handleMessage(Message message, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { + public void handleMessage(Message message, AcknowledgementHandler acknowledgeHandler) { String correlationId = message.getCorrelationId(); Collector collector = collectorsByCorrelationId.get(correlationId); if (collector != null) { diff --git a/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java b/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java index 00d706cb..05e0ec28 100644 --- a/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java +++ b/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java @@ -1,6 +1,6 @@ package io.github.tcdl.msb.events; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.Requester; import io.github.tcdl.msb.api.message.Acknowledge; @@ -13,9 +13,9 @@ */ public class EventHandlers { - private BiConsumer onAcknowledge = (acknowledge, ackHandler) -> {}; - private BiConsumer onResponse = (acknowledge, ackHandler) -> {}; - private BiConsumer onRawResponse = (acknowledge, ackHandler) -> {}; + private BiConsumer onAcknowledge = (acknowledge, ackHandler) -> {}; + private BiConsumer onResponse = (acknowledge, ackHandler) -> {}; + private BiConsumer onRawResponse = (acknowledge, ackHandler) -> {}; private Callback onEnd = messages -> {}; /** @@ -23,7 +23,7 @@ public class EventHandlers { * * @return acknowledge callback */ - public BiConsumer onAcknowledge() { + public BiConsumer onAcknowledge() { return onAcknowledge; } @@ -32,7 +32,7 @@ public BiConsumer onAcknowl * * @param onAcknowledge callback */ - public EventHandlers onAcknowledge(BiConsumer onAcknowledge) { + public EventHandlers onAcknowledge(BiConsumer onAcknowledge) { this.onAcknowledge = onAcknowledge; return this; } @@ -42,7 +42,7 @@ public EventHandlers onAcknowledge(BiConsumer onResponse() { + public BiConsumer onResponse() { return onResponse; } @@ -51,7 +51,7 @@ public BiConsumer onResponse() { * * @param onResponse callback */ - public EventHandlers onResponse(BiConsumer onResponse) { + public EventHandlers onResponse(BiConsumer onResponse) { this.onResponse = onResponse; return this; } @@ -61,7 +61,7 @@ public EventHandlers onResponse(BiConsumer onRawResponse() { + public BiConsumer onRawResponse() { return onRawResponse; } @@ -70,7 +70,7 @@ public BiConsumer onRawResponse * * @param onRawResponse callback */ - public EventHandlers onRawResponse(BiConsumer onRawResponse) { + public EventHandlers onRawResponse(BiConsumer onRawResponse) { this.onRawResponse = onRawResponse; return this; } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java index dcc34b49..8c0704f5 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java @@ -1,7 +1,6 @@ package io.github.tcdl.msb.impl; -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.adapters.ConsumerAdapter.AcknowledgementHandler; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Message; @@ -15,9 +14,9 @@ public class NoopResponderImpl implements Responder { private static final Logger LOG = LoggerFactory.getLogger(NoopResponderImpl.class); private Message originalMessage; - private ConsumerAdapter.AcknowledgementHandler ackHandler; + private AcknowledgementHandler ackHandler; - public NoopResponderImpl(Message originalMessage, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { + public NoopResponderImpl(Message originalMessage, AcknowledgementHandler acknowledgeHandler) { this.originalMessage = originalMessage; this.ackHandler = ackHandler; } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java index 5ff2e355..2ee3c63c 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java @@ -1,7 +1,7 @@ package io.github.tcdl.msb.impl; import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.RequestOptions; @@ -126,7 +126,7 @@ private boolean isWaitForResponses() { * {@inheritDoc} */ @Override - public Requester onAcknowledge(BiConsumer acknowledgeHandler) { + public Requester onAcknowledge(BiConsumer acknowledgeHandler) { eventHandlers.onAcknowledge(acknowledgeHandler); return this; } @@ -135,7 +135,7 @@ public Requester onAcknowledge(BiConsumer onResponse(BiConsumer responseHandler) { + public Requester onResponse(BiConsumer responseHandler) { eventHandlers.onResponse(responseHandler); return this; } @@ -143,7 +143,7 @@ public Requester onResponse(BiConsumer onRawResponse(BiConsumer responseHandler) { + @Override public Requester onRawResponse(BiConsumer responseHandler) { eventHandlers.onRawResponse(responseHandler); return this; } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java index 15ff8862..b6eb7a01 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java @@ -2,8 +2,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.Producer; -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.adapters.ConsumerAdapter.AcknowledgementHandler; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Acknowledge.Builder; @@ -24,10 +23,10 @@ public class ResponderImpl implements Responder { private ChannelManager channelManager; private MessageFactory messageFactory; private Message.Builder messageBuilder; - private ConsumerAdapter.AcknowledgementHandler ackHandler; + private AcknowledgementHandler ackHandler; public ResponderImpl(MessageTemplate messageTemplate, Message originalMessage, - ConsumerAdapter.AcknowledgementHandler ackHandler, MsbContextImpl msbContext) { + AcknowledgementHandler ackHandler, MsbContextImpl msbContext) { validateReceivedMessage(originalMessage); this.responderId = Utils.generateId(); this.originalMessage = originalMessage; diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java index 81a0a759..81727344 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java @@ -1,7 +1,7 @@ package io.github.tcdl.msb.impl; import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.ResponderServer; @@ -67,7 +67,7 @@ public ResponderServer listen() { return this; } - Responder createResponder(Message incomingMessage, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { + Responder createResponder(Message incomingMessage, AcknowledgementHandler acknowledgeHandler) { if (isResponseNeeded(incomingMessage)) { return new ResponderImpl(messageTemplate, incomingMessage, acknowledgeHandler, msbContext); } else { diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java b/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java index 4951314d..8d85d587 100644 --- a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java +++ b/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java @@ -2,7 +2,7 @@ import static io.github.tcdl.msb.support.Utils.TOPIC_ANNOUNCE; import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.ObjectFactory; import io.github.tcdl.msb.api.exception.JsonConversionException; @@ -88,7 +88,7 @@ void onHeartbeatResponses(List heartbeatResponses) { } } - void onAnnounce(Message announcementMessage, ConsumerAdapter.AcknowledgementHandler acknowledgeHandler) { + void onAnnounce(Message announcementMessage, AcknowledgementHandler acknowledgeHandler) { LOG.debug(String.format("Handling announcement message %s...", announcementMessage)); boolean successfullyAggregated = aggregateInfo(masterAggregatorStats, announcementMessage); diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index f5d2740e..fa48127f 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -6,6 +6,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.MetaMessage; @@ -185,8 +186,8 @@ private Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) { .build(); } - private ConsumerAdapter.AcknowledgementHandler createAcknowledgementHandler() { - return new ConsumerAdapter.AcknowledgementHandler() { + private AcknowledgementHandler createAcknowledgementHandler() { + return new AcknowledgementHandler() { @Override public void confirmMessage() { } diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java index 90984ecd..be55c775 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java @@ -15,7 +15,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.exception.JsonConversionException; @@ -151,16 +151,16 @@ public void testHandleResponse() { String bodyText = "some body"; Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); @SuppressWarnings("unchecked") - BiConsumer onResponse = mock(BiConsumer.class); + BiConsumer onResponse = mock(BiConsumer.class); @SuppressWarnings("unchecked") - BiConsumer onRawResponse = mock(BiConsumer.class); + BiConsumer onRawResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); when(eventHandlers.onRawResponse()).thenReturn(onRawResponse); Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() { }); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); // method under test collector.handleMessage(responseMessage, ackHandler); @@ -180,9 +180,9 @@ public void testHandleResponseConversionFailed() { String bodyText = "some body"; Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); @SuppressWarnings("unchecked") - BiConsumer, ConsumerAdapter.AcknowledgementHandler> onResponse = mock(BiConsumer.class); + BiConsumer, AcknowledgementHandler> onResponse = mock(BiConsumer.class); @SuppressWarnings("unchecked") - BiConsumer onRawResponse = mock(BiConsumer.class); + BiConsumer onRawResponse = mock(BiConsumer.class); @SuppressWarnings("unchecked") EventHandlers> eventHandlers = mock(EventHandlers.class); @@ -193,7 +193,7 @@ public void testHandleResponseConversionFailed() { Collector> collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, payloadTypeReference); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); // make sure that onRawResponse is called even if conversion of payload to custom type fails try { @@ -207,11 +207,11 @@ public void testHandleResponseConversionFailed() { @Test @SuppressWarnings({ "rawtypes", "unchecked" }) public void testHandleResponseReceivedAck() { - BiConsumer onAck = mock(BiConsumer.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Collector collector = createCollector(); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); collector.handleMessage(responseMessageWithAck, ackHandler); verify(onAck).accept(responseMessageWithAck.getAck(), ackHandler); @@ -226,7 +226,7 @@ public void testHandleResponseEndEventNoResponsesRemaining() { when(eventHandlers.onEnd()).thenReturn(onEnd); Collector collector = createCollector(); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); collector.handleMessage(responseMessageWithAck, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(anyInt(), any(Collector.class)); @@ -247,13 +247,13 @@ public void testHandleResponseLastResponse() { when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout); when(requestOptionsMock.getWaitForResponses()).thenReturn(1); - BiConsumer onResponse = mock(BiConsumer.class); + BiConsumer onResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); Collector collector = createCollector(); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); collector.handleMessage(responseMessage, ackHandler); RestPayload expectedPayload = new RestPayload.Builder() @@ -278,7 +278,7 @@ public void testHandleResponseWaitForOneMoreResponse() { when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout); when(requestOptionsMock.getWaitForResponses()).thenReturn(2); - BiConsumer onResponse = mock(BiConsumer.class); + BiConsumer onResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -290,7 +290,7 @@ public void testHandleResponseWaitForOneMoreResponse() { .withBody(bodyText) .build(); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); //send first response collector.handleMessage(responseMessage, ackHandler); verify(onResponse).accept(expectedPayload, ackHandler); @@ -317,7 +317,7 @@ public void testHandleResponseNoResponsesRemainingButAwaitAck() { when(requestOptionsMock.getResponseTimeout()).thenReturn(0); when(requestOptionsMock.getWaitForResponses()).thenReturn(0); - BiConsumer onAck = mock(BiConsumer.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Callback onEnd = mock(Callback.class); @@ -326,7 +326,7 @@ public void testHandleResponseNoResponsesRemainingButAwaitAck() { Collector collector = createCollector(); collector.listenForResponses(); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); //send payload response collector.handleMessage(responseMessage, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(anyInt(), eq(collector)); @@ -347,7 +347,7 @@ public void testHandleResponseReceivedPayloadButAwaitAck() { when(requestOptionsMock.getResponseTimeout()).thenReturn(0); when(requestOptionsMock.getWaitForResponses()).thenReturn(1); - BiConsumer onAck = mock(BiConsumer.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -355,7 +355,7 @@ public void testHandleResponseReceivedPayloadButAwaitAck() { Collector collector = createCollector(); collector.listenForResponses(); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); //send payload response collector.handleMessage(responseMessage, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(0), eq(collector)); @@ -376,7 +376,7 @@ public void testHandleResponseNoResponsesRemainingAndWaitUntilAckBeforeNow() { when(requestOptionsMock.getResponseTimeout()).thenReturn(0); when(requestOptionsMock.getWaitForResponses()).thenReturn(0); - BiConsumer onAck = mock(BiConsumer.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -384,7 +384,7 @@ public void testHandleResponseNoResponsesRemainingAndWaitUntilAckBeforeNow() { Collector collector = createCollector(); collector.listenForResponses(); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); //send payload response collector.handleMessage(responseMessage, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(0), eq(collector)); @@ -411,7 +411,7 @@ public void testHandleResponseReceivedAckWithSameTimeoutValue() { Acknowledge ack = new Acknowledge.Builder().withResponderId(Utils.generateId()).withResponsesRemaining(0).withTimeoutMs(timeoutMs).build(); Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); collector.handleMessage(responseMessageWithAck, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(timeoutMs), eq(collector)); @@ -439,7 +439,7 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndNoResponsesRemaini .build(); Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId()); - ConsumerAdapter.AcknowledgementHandler ackHandler = mock(ConsumerAdapter.AcknowledgementHandler.class); + AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); collector.handleMessage(responseMessageWithAck, ackHandler); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(timeoutMsInAck), eq(collector)); @@ -536,7 +536,7 @@ public void testHandleResponseEnsureResponsesRemainingIsDecreased() { when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout); when(requestOptionsMock.getWaitForResponses()).thenReturn(responsesRemaining); - BiConsumer onResponse = mock(BiConsumer.class); + BiConsumer onResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); diff --git a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java b/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java index 8b89bd17..8e61e3cf 100644 --- a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java +++ b/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java @@ -6,7 +6,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; import io.github.tcdl.msb.api.ObjectFactory; import io.github.tcdl.msb.api.RequestOptions; @@ -40,7 +40,7 @@ public class HeartbeatTaskTest { public void setUp() { when(mockObjectFactory.createRequester(anyString(), any(RequestOptions.class))).thenReturn(mockRequester); @SuppressWarnings("unchecked") - BiConsumer responseHandler = any(BiConsumer.class); + BiConsumer responseHandler = any(BiConsumer.class); when(mockRequester.onRawResponse(responseHandler)).thenReturn(mockRequester); @SuppressWarnings("unchecked") Callback endHandler = any(Callback.class); From f13ae707e3a83fb0e10d8e989737177356fe79eb Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Sun, 6 Dec 2015 19:40:12 +0200 Subject: [PATCH 015/226] Added ResponderContext --- .../MultipleRequesterResponder.java | 7 +-- .../msb/acceptance/MultipleResponder.java | 7 +-- .../acceptance/RequesterResponderTest.java | 4 +- .../tcdl/msb/acceptance/SimpleResponder.java | 4 +- .../bdd/steps/RequesterResponderSteps.java | 25 +++++----- .../tcdl/msb/api/AcknowledgementHandler.java | 4 +- .../io/github/tcdl/msb/api/Responder.java | 10 ---- .../github/tcdl/msb/api/ResponderContext.java | 25 ++++++++++ .../github/tcdl/msb/api/ResponderServer.java | 6 ++- .../github/tcdl/msb/events/EventHandlers.java | 6 +++ .../tcdl/msb/impl/NoopResponderImpl.java | 17 +------ .../tcdl/msb/impl/ResponderContextImpl.java | 50 +++++++++++++++++++ .../github/tcdl/msb/impl/ResponderImpl.java | 12 +---- .../tcdl/msb/impl/ResponderServerImpl.java | 34 ++++++++----- .../agent/DefaultChannelMonitorAgent.java | 2 +- .../tcdl/msb/api/RequesterResponderIT.java | 29 ++++++----- .../io/github/tcdl/msb/api/ResponderIT.java | 6 +-- .../tcdl/msb/impl/ResponderImplTest.java | 6 +-- .../msb/impl/ResponderServerImplTest.java | 50 +++++++++++++------ .../tcdl/msb/examples/DateExtractor.java | 4 +- .../tcdl/msb/examples/FacetsAggregator.java | 7 ++- .../github/tcdl/msb/examples/PongService.java | 6 ++- 22 files changed, 205 insertions(+), 116 deletions(-) create mode 100644 core/src/main/java/io/github/tcdl/msb/api/ResponderContext.java create mode 100644 core/src/main/java/io/github/tcdl/msb/impl/ResponderContextImpl.java diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java index 57228a48..ad8b0797 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java @@ -1,13 +1,14 @@ package io.github.tcdl.msb.acceptance; import io.github.tcdl.msb.api.Requester; -import org.apache.commons.lang3.concurrent.BasicThreadFactory; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; + public class MultipleRequesterResponder { private static final Integer NUMBER_OF_RESPONSES = 1; @@ -31,7 +32,7 @@ public void runMultipleRequesterResponder() { ExecutorService executor = Executors.newFixedThreadPool(2, threadFactory); - util.createResponderServer(responderNamespace, (request, responder) -> { + util.createResponderServer(responderNamespace, (request, responderContext) -> { System.out.print(">>> REQUEST: " + request); Future futureRequester1 = createAndRunRequester(executor, requesterNamespace1); @@ -44,7 +45,7 @@ public void runMultipleRequesterResponder() { executor.shutdownNow(); - responder.send("response from MultipleRequesterResponder:" + (result1 + result2)); + responderContext.getResponder().send("response from MultipleRequesterResponder:" + (result1 + result2)); }, String.class) .listen(); } diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java index e60345e3..9a632e3e 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java @@ -1,6 +1,5 @@ package io.github.tcdl.msb.acceptance; -import com.fasterxml.jackson.core.type.TypeReference; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.MsbContext; import io.github.tcdl.msb.api.MsbContextBuilder; @@ -8,6 +7,8 @@ import java.util.Map; +import com.fasterxml.jackson.core.type.TypeReference; + public class MultipleResponder { public static void main(String... args) { @@ -19,14 +20,14 @@ public static void main(String... args) { public static void runResponder(String namespace, MsbContext msbContext) { MessageTemplate options = new MessageTemplate(); - msbContext.getObjectFactory().createResponderServer(namespace, options, (request, responder) -> { + msbContext.getObjectFactory().createResponderServer(namespace, options, (request, responderContext) -> { Map requestBody = request.getBody(); System.out.println(">>> GOT request: " + requestBody); String requestId = (String) requestBody.get("requestId"); SearchResponse response = new SearchResponse(requestId, "response"); System.out.println(">>> SENDING response in request to " + requestId); - responder.send(new RestPayload.Builder() + responderContext.getResponder().send(new RestPayload.Builder() .withBody(response) .build()); }, new TypeReference>() {}) diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java index 549192e3..f2da486b 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java @@ -1,6 +1,7 @@ package io.github.tcdl.msb.acceptance; import io.github.tcdl.msb.api.Requester; +import io.github.tcdl.msb.api.Responder; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -28,8 +29,9 @@ public boolean isPassed() { public void runRequesterResponder() throws Exception { helper.initDefault(); // running responder server - helper.createResponderServer(NAMESPACE, (request, responder) -> { + helper.createResponderServer(NAMESPACE, (request, responderContext) -> { System.out.println(">>> REQUEST: " + request); + Responder responder = responderContext.getResponder(); responder.sendAck(1000, NUMBER_OF_RESPONSES); responder.send("Pong"); }, String.class) diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/SimpleResponder.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/SimpleResponder.java index 5f72859d..5c76bad5 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/SimpleResponder.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/SimpleResponder.java @@ -14,10 +14,10 @@ public class SimpleResponder { public void runSimpleResponderExample() { helper.initDefault(); - helper.createResponderServer(namespace, (request, responder) -> { + helper.createResponderServer(namespace, (request, responderContext) -> { System.out.print(">>> REQUEST: " + request); Thread.sleep(500); - responder.send(namespace + ":" + "SimpleResponder"); + responderContext.getResponder().send(namespace + ":" + "SimpleResponder"); }, String.class) .listen(); } diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java index dad161a3..9eb331fe 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java @@ -1,9 +1,16 @@ package io.github.tcdl.msb.acceptance.bdd.steps; -import com.fasterxml.jackson.databind.ObjectMapper; +import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME; import io.github.tcdl.msb.api.Requester; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.support.Utils; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import org.hamcrest.Matchers; import org.jbehave.core.annotations.Given; import org.jbehave.core.annotations.Then; @@ -12,13 +19,7 @@ import org.jbehave.core.model.OutcomesTable; import org.junit.Assert; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME; +import com.fasterxml.jackson.databind.ObjectMapper; /** * Steps to send requests and respond with predifined responses @@ -44,17 +45,17 @@ public void createResponderServer(String namespace) { public void createResponderServer(String contextName, String namespace) { ObjectMapper mapper = helper.getPayloadMapper(contextName); countRequestsReceived = 0; - helper.createResponderServer(contextName, namespace, (request, responder) -> { + helper.createResponderServer(contextName, namespace, (request, responderContext) -> { if (responseBody != null) { countRequestsReceived++; boolean isSendResponse = true; switch (nextRequestAckType.orElseGet(()->"auto")) { case "confirm": - responder.getAcknowledgementHandler().confirmMessage(); + responderContext.getAcknowledgementHandler().confirmMessage(); break; case "reject": - responder.getAcknowledgementHandler().rejectMessage(); + responderContext.getAcknowledgementHandler().rejectMessage(); isSendResponse = false; break; } @@ -65,7 +66,7 @@ public void createResponderServer(String contextName, String namespace) { RestPayload payload = new RestPayload.Builder() .withBody(Utils.fromJson(responseBody, Map.class, mapper)) .build(); - responder.send(payload); + responderContext.getResponder().send(payload); } } }).listen(); diff --git a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java index 453b5b87..cf1b225c 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java +++ b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java @@ -7,12 +7,12 @@ public interface AcknowledgementHandler { /** * Inform server that a message was confirmed by consumer. - * Server should consider messages acknowledged once delivered + * Server should consider message acknowledged once delivered */ void confirmMessage(); /** - * Inform server that a message was reject by consumer. + * Inform server that a message was rejected by consumer. * AMQP Server may requeue message or delete it from queue depending on the * requeueRejectedMessages configuration option */ diff --git a/core/src/main/java/io/github/tcdl/msb/api/Responder.java b/core/src/main/java/io/github/tcdl/msb/api/Responder.java index d4802501..cf6c754d 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/Responder.java +++ b/core/src/main/java/io/github/tcdl/msb/api/Responder.java @@ -1,6 +1,5 @@ package io.github.tcdl.msb.api; -import io.github.tcdl.msb.api.message.Message; /** * Responsible for creating responses and acknowledgements and sending them to the bus. @@ -22,13 +21,4 @@ public interface Responder { */ void send(Object responsePayload); - /** - * @return original message to send a response to - */ - Message getOriginalMessage(); - - /** - * @return AcknowledgementHandler for explicit confirm/reject incoming messages - */ - AcknowledgementHandler getAcknowledgementHandler(); } \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/api/ResponderContext.java b/core/src/main/java/io/github/tcdl/msb/api/ResponderContext.java new file mode 100644 index 00000000..8568cef7 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/api/ResponderContext.java @@ -0,0 +1,25 @@ +package io.github.tcdl.msb.api; + +import io.github.tcdl.msb.api.message.Message; + +/** + * Provides access to Responder Context. + */ +public interface ResponderContext { + + /** + * @return Responder instance + */ + Responder getResponder(); + + /** + * @return AcknowledgementHandler for explicit confirm/reject incoming messages + */ + AcknowledgementHandler getAcknowledgementHandler(); + + /** + * @return original message to send a response to + */ + Message getOriginalMessage(); + +} diff --git a/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java b/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java index 860bac5e..cce3fb6a 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java +++ b/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java @@ -24,10 +24,12 @@ interface RequestHandler { /** * Execute business logic and send response. * @param request request received from a bus - * @param responder object of type {@link Responder} which will be used for sending response + * @param responderContext object of type {@link ResponderContext} which will + * provide access to {@link Responder} that used for sending response and + * {@link AcknowledgementHandler} that used for explicit confirm/reject received request * @throws Exception if some problems during execution business logic or sending response were occurred */ - void process(T request, Responder responder) throws Exception; + void process(T request, ResponderContext responderContext) throws Exception; } } diff --git a/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java b/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java index 05e0ec28..7e5865f3 100644 --- a/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java +++ b/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java @@ -31,6 +31,7 @@ public BiConsumer onAcknowledge() { * Registered callback for Acknowledge event. * * @param onAcknowledge callback + * @return EventHandlers */ public EventHandlers onAcknowledge(BiConsumer onAcknowledge) { this.onAcknowledge = onAcknowledge; @@ -41,6 +42,7 @@ public EventHandlers onAcknowledge(BiConsumer onResponse() { return onResponse; @@ -50,6 +52,7 @@ public BiConsumer onResponse() { * Registered callback for Response event. * * @param onResponse callback + * @return EventHandlers */ public EventHandlers onResponse(BiConsumer onResponse) { this.onResponse = onResponse; @@ -60,6 +63,7 @@ public EventHandlers onResponse(BiConsumer onResponse * Return callback registered for Response event. * * @return response callback + * @return EventHandlers */ public BiConsumer onRawResponse() { return onRawResponse; @@ -69,6 +73,7 @@ public BiConsumer onRawResponse() { * Registered callback for Response event. * * @param onRawResponse callback + * @return EventHandlers */ public EventHandlers onRawResponse(BiConsumer onRawResponse) { this.onRawResponse = onRawResponse; @@ -88,6 +93,7 @@ public Callback onEnd() { * Registered callback for End event. * * @param onEnd callback + * @return EventHandlers */ public EventHandlers onEnd(Callback onEnd) { this.onEnd = onEnd; diff --git a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java index 8c0704f5..424209b1 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java @@ -1,6 +1,5 @@ package io.github.tcdl.msb.impl; -import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Message; @@ -14,11 +13,9 @@ public class NoopResponderImpl implements Responder { private static final Logger LOG = LoggerFactory.getLogger(NoopResponderImpl.class); private Message originalMessage; - private AcknowledgementHandler ackHandler; - public NoopResponderImpl(Message originalMessage, AcknowledgementHandler acknowledgeHandler) { + public NoopResponderImpl(Message originalMessage) { this.originalMessage = originalMessage; - this.ackHandler = ackHandler; } /** {@inheritDoc} */ @@ -33,16 +30,4 @@ public void send(Object responsePayload) { LOG.error("Cannot send response because response topic is unknown. Incoming message: {}", originalMessage); } - /** {@inheritDoc} */ - @Override - public Message getOriginalMessage() { - return originalMessage; - } - - @Override - public AcknowledgementHandler getAcknowledgementHandler() { - return ackHandler; - } - - } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderContextImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderContextImpl.java new file mode 100644 index 00000000..6967fbb3 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderContextImpl.java @@ -0,0 +1,50 @@ +package io.github.tcdl.msb.impl; + +import io.github.tcdl.msb.api.AcknowledgementHandler; +import io.github.tcdl.msb.api.Responder; +import io.github.tcdl.msb.api.ResponderContext; +import io.github.tcdl.msb.api.message.Message; + +/** + * Implementation of {@link ResponderContext} Provide access to {@link Responder} + * that used for sending response and {@link AcknowledgementHandler} that used + * for explicit confirm/reject received request + */ +public class ResponderContextImpl implements ResponderContext { + + private final Responder responder; + private final AcknowledgementHandler acknowledgementHandler; + private final Message originalMessage; + + public ResponderContextImpl(Responder responder, AcknowledgementHandler acknowledgementHandler, Message originalMessage) { + super(); + this.responder = responder; + this.acknowledgementHandler = acknowledgementHandler; + this.originalMessage = originalMessage; + } + + /** + * {@inheritDoc} + */ + @Override + public Responder getResponder() { + return responder; + } + + /** + * {@inheritDoc} + */ + @Override + public AcknowledgementHandler getAcknowledgementHandler() { + return acknowledgementHandler; + } + + /** + * {@inheritDoc} + */ + @Override + public Message getOriginalMessage() { + return originalMessage; + } + +} diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java index b6eb7a01..17a7ec66 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java @@ -26,7 +26,7 @@ public class ResponderImpl implements Responder { private AcknowledgementHandler ackHandler; public ResponderImpl(MessageTemplate messageTemplate, Message originalMessage, - AcknowledgementHandler ackHandler, MsbContextImpl msbContext) { + MsbContextImpl msbContext) { validateReceivedMessage(originalMessage); this.responderId = Utils.generateId(); this.originalMessage = originalMessage; @@ -74,14 +74,4 @@ private void validateReceivedMessage(Message originalMessage) { Validate.notNull(originalMessage.getTopics(), "the 'originalMessage.topics' must not be null"); } - /** {@inheritDoc} */ - public Message getOriginalMessage() { - return this.originalMessage; - } - - @Override - public AcknowledgementHandler getAcknowledgementHandler() { - return this.ackHandler; - } - } \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java index 81727344..656152ea 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java @@ -4,6 +4,7 @@ import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.Responder; +import io.github.tcdl.msb.api.ResponderContext; import io.github.tcdl.msb.api.ResponderServer; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Message; @@ -60,32 +61,37 @@ public ResponderServer listen() { channelManager.subscribe(namespace, (incomingMessage, acknowledgeHandler) -> { LOG.debug("[{}] Received message with id: [{}]", namespace, incomingMessage.getId()); - Responder responder = createResponder(incomingMessage, acknowledgeHandler); - onResponder(responder); + Responder responder = createResponder(incomingMessage); + ResponderContext responderContext = createResponderContext(responder, acknowledgeHandler, incomingMessage); + onResponder(responderContext); }); return this; } - Responder createResponder(Message incomingMessage, AcknowledgementHandler acknowledgeHandler) { + Responder createResponder(Message incomingMessage) { if (isResponseNeeded(incomingMessage)) { - return new ResponderImpl(messageTemplate, incomingMessage, acknowledgeHandler, msbContext); + return new ResponderImpl(messageTemplate, incomingMessage, msbContext); } else { - return new NoopResponderImpl(incomingMessage, acknowledgeHandler); + return new NoopResponderImpl(incomingMessage); } } - void onResponder(Responder responder) { - Message originalMessage = responder.getOriginalMessage(); + ResponderContext createResponderContext(Responder responder, AcknowledgementHandler acknowledgeHandler, Message incomingMessage) { + return new ResponderContextImpl(responder, acknowledgeHandler, incomingMessage); + } + + void onResponder(ResponderContext responderContext) { + Message originalMessage = responderContext.getOriginalMessage(); Object rawPayload = originalMessage.getRawPayload(); try { T request = Utils.convert(rawPayload, payloadTypeReference, payloadMapper); LOG.debug("[{}] Process message with id: [{}]", namespace, originalMessage.getId()); - requestHandler.process(request, responder); + requestHandler.process(request, responderContext); } catch (JsonConversionException conversionEx) { - errorHandler(responder, conversionEx, PAYLOAD_CONVERSION_ERROR_CODE); + errorHandler(responderContext, conversionEx, PAYLOAD_CONVERSION_ERROR_CODE); } catch (Exception internalEx) { - errorHandler(responder, internalEx, INTERNAL_SERVER_ERROR_CODE); + errorHandler(responderContext, internalEx, INTERNAL_SERVER_ERROR_CODE); } } @@ -93,13 +99,15 @@ private boolean isResponseNeeded(Message incomingMessage) { return incomingMessage.getTopics().getResponse() != null; } - private void errorHandler(Responder responder, Exception exception, int errorStatusCode) { - Message originalMessage = responder.getOriginalMessage(); + private void errorHandler(ResponderContext responderContext, Exception exception, int errorStatusCode) { + Message originalMessage = responderContext.getOriginalMessage(); LOG.error("[{}] Error while processing message with id: [{}]", namespace, originalMessage.getId(), exception); RestPayload responsePayload = new RestPayload.Builder() .withStatusCode(errorStatusCode) .withStatusMessage(exception.getMessage()) .build(); - responder.send(responsePayload); + responderContext.getResponder().send(responsePayload); + //Confirm message for prevention requeue message with incorrect structure + responderContext.getAcknowledgementHandler().confirmMessage(); } } \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java b/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java index e7217e9a..cec48ae5 100644 --- a/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java +++ b/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java @@ -58,7 +58,7 @@ public static void start(MsbContextImpl msbContext) { public DefaultChannelMonitorAgent start() { channelManager.subscribe(Utils.TOPIC_HEARTBEAT, // Launch listener for heartbeat topic (message, acknowledgeHandler) -> { - Responder responder = new ResponderImpl(null, message, acknowledgeHandler, msbContext); + Responder responder = new ResponderImpl(null, message, msbContext); RestPayload payload = new RestPayload.Builder>() .withBody(topicInfoMap) .build(); diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java index af56e0a2..3c33fba5 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java @@ -87,8 +87,8 @@ public void testResponderAnswerWithAckRequesterReceiveAck() throws Exception { //listen for message and send ack MsbContextImpl serverMsbContext = TestUtils.createSimpleMsbContext(); - serverMsbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, response) -> { - response.sendAck(100, 2); + serverMsbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, responderContext) -> { + responderContext.getResponder().sendAck(100, 2); ackSend.countDown(); }) .listen(); @@ -126,8 +126,8 @@ public void testResponderAnswerWithResponseRequesterReceiveCustomPayloadResponse //listen for message and send response MsbContextImpl serverMsbContext = TestUtils.createSimpleMsbContext(); - serverMsbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, response) -> { - response.send(responsePayload); + serverMsbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, responderContext) -> { + responderContext.getResponder().send(responsePayload); respSent.countDown(); }, String.class).listen(); @@ -163,10 +163,11 @@ public void testResponderCommunicationWithAck() throws Exception { .listen(); MsbContextImpl serverTwoMsbContext = TestUtils.createSimpleMsbContext(); - serverTwoMsbContext.getObjectFactory().createResponderServer(namespace2, responderServerTwoMessageOptions, (request, response) -> { - response.sendAck(100, 2); - ackSent.countDown(); - }) + serverTwoMsbContext.getObjectFactory().createResponderServer(namespace2, responderServerTwoMessageOptions, + (request, responderContext) -> { + responderContext.getResponder().sendAck(100, 2); + ackSent.countDown(); + }) .listen(); MockAdapter.pushRequestMessage(namespace1, @@ -216,10 +217,11 @@ public void testMultipleRequesterListenForAcks() throws Exception { MsbContextImpl serverMsbContext = TestUtils.createSimpleMsbContext(); Random randomAckValue = new Random(); randomAckValue.ints(); - serverMsbContext.getObjectFactory().createResponderServer(namespace, requestOptions.getMessageTemplate(), (request, response) -> { - response.sendAck(randomAckValue.nextInt(), randomAckValue.nextInt()); - ackSend.countDown(); - }) + serverMsbContext.getObjectFactory().createResponderServer(namespace, requestOptions.getMessageTemplate(), + (request, responderContext) -> { + responderContext.getResponder().sendAck(randomAckValue.nextInt(), randomAckValue.nextInt()); + ackSend.countDown(); + }) .listen(); assertTrue("Message ack was not send", ackSend.await(MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); @@ -238,7 +240,8 @@ public void testRequestMessageCollectorUnsubscribeAfterResponsesAndSubscribeAgai Thread serverListenThread = new Thread(() -> { msbContext.getObjectFactory().createResponderServer(namespace, requestOptionsWaitResponse.getMessageTemplate(), - (request, response) -> response.send("payload from test : testRequestMessageCollectorUnsubscribeAfterResponsesAndSubscribeAgain") + (request, responderContext) -> + responderContext.getResponder().send("payload from test : testRequestMessageCollectorUnsubscribeAfterResponsesAndSubscribeAgain") ) .listen(); }); diff --git a/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java index 48fcb952..a6601798 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java @@ -49,7 +49,7 @@ public void testCreateAckMessage() throws Exception { MessageTemplate messageOptions = TestUtils.createSimpleMessageTemplate(); Message originalMessage = TestUtils.createSimpleRequestMessage(namespace); - Responder responder = new ResponderImpl(messageOptions, originalMessage, null, msbContext); + Responder responder = new ResponderImpl(messageOptions, originalMessage, msbContext); responder.sendAck(ackTimeout, responsesRemaining); @@ -90,7 +90,7 @@ public void testCreateResponseMessage() throws Exception { MessageTemplate messageOptions = TestUtils.createSimpleMessageTemplate(); Message originalMessage = TestUtils.createSimpleRequestMessage(namespace); - Responder responder = new ResponderImpl(messageOptions, originalMessage, null, msbContext); + Responder responder = new ResponderImpl(messageOptions, originalMessage, msbContext); RestPayload responsePayload = TestUtils.createSimpleResponsePayload(); responder.send(responsePayload); @@ -107,7 +107,7 @@ public void testCreateResponseMessageWithTags() throws Exception { String dynamicTagOriginal = "dynamic-tag-original"; Message originalMessage = TestUtils.createSimpleRequestMessageWithTags(namespace, dynamicTagOriginal); - Responder responder = new ResponderImpl(messageOptions, originalMessage, null, msbContext); + Responder responder = new ResponderImpl(messageOptions, originalMessage, msbContext); RestPayload responsePayload = TestUtils.createSimpleResponsePayload(); responder.send(responsePayload); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java index 67ee94c2..65ccb2b4 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java @@ -58,14 +58,14 @@ public void setUp() { when(msbContextSpy.getMessageFactory()).thenReturn(spyMessageFactory); when(mockChannelManager.findOrCreateProducer(anyString())).thenReturn(mockProducer); - responder = new ResponderImpl(messageTemplate, originalMessage, null, msbContextSpy); + responder = new ResponderImpl(messageTemplate, originalMessage, msbContextSpy); } @Test public void testResponderConstructorOk() { MsbContextImpl context = TestUtils.createSimpleMsbContext(); Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - new ResponderImpl(messageTemplate, originalMessage, null, context); + new ResponderImpl(messageTemplate, originalMessage, context); } @Test @@ -101,7 +101,7 @@ public void testProducerPublishUseCorrectPayload() { @Test public void testProducerPublishWithTags() { String[] tags = new String[]{"tag1", "tag2"}; - responder = new ResponderImpl(TestUtils.createSimpleMessageTemplate(tags), originalMessage, null, msbContextSpy); + responder = new ResponderImpl(TestUtils.createSimpleMessageTemplate(tags), originalMessage, msbContextSpy); ArgumentCaptor argument = ArgumentCaptor.forClass(Message.class); responder.send(TestUtils.createSimpleRequestPayload()); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java index 343d6255..9bb189b8 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java @@ -14,14 +14,18 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.Producer; +import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Responder; +import io.github.tcdl.msb.api.ResponderContext; import io.github.tcdl.msb.api.ResponderServer; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.support.TestUtils; +import java.util.Map; + import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -46,8 +50,9 @@ public void setUp() { @Test public void testResponderServerProcessPayloadSuccess() throws Exception { - ResponderServer.RequestHandler handler = (request, responder) -> { - }; + ResponderServer.RequestHandler, Object, Map>> + handler = (request, responderContext) -> { + }; ArgumentCaptor subscriberCaptor = ArgumentCaptor.forClass(MessageHandler.class); ChannelManager spyChannelManager = spy(msbContext.getChannelManager()); @@ -55,14 +60,17 @@ public void testResponderServerProcessPayloadSuccess() throws Exception { when(spyMsbContext.getChannelManager()).thenReturn(spyChannelManager); - ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, requestOptions.getMessageTemplate(), spyMsbContext, handler, new TypeReference() {}); + ResponderServerImpl, Object, Map>> responderServer = ResponderServerImpl + .create(TOPIC, requestOptions.getMessageTemplate(), spyMsbContext, handler, + new TypeReference, Object, Map>>() {}); ResponderServerImpl spyResponderServer = (ResponderServerImpl) spy(responderServer).listen(); verify(spyChannelManager).subscribe(anyString(), subscriberCaptor.capture()); Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); + AcknowledgementHandler mockAcknowledgeHandler = mock(AcknowledgementHandler.class); + subscriberCaptor.getValue().handleMessage(originalMessage, null); verify(spyResponderServer).onResponder(anyObject()); @@ -75,7 +83,7 @@ public void testResponderServerProcessErrorNoHandler() throws Exception { @Test public void testResponderServerProcessUnexpectedPayload() throws Exception { - ResponderServer.RequestHandler handler = (request, responder) -> { + ResponderServer.RequestHandler handler = (request, responderContext) -> { }; String bodyText = "some body"; @@ -88,8 +96,12 @@ public void testResponderServerProcessUnexpectedPayload() throws Exception { // simulate incoming request ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(RestPayload.class); ResponderImpl responder = spy( - new ResponderImpl(messageTemplate, incomingMessage, null, msbContext)); - responderServer.onResponder(responder); + new ResponderImpl(messageTemplate, incomingMessage, msbContext)); + + AcknowledgementHandler acknowledgeHandler = mock(AcknowledgementHandler.class); + ResponderContext responderContext = responderServer.createResponderContext(responder, acknowledgeHandler, incomingMessage); + + responderServer.onResponder(responderContext); verify(responder).send(responseCaptor.capture()); assertEquals(ResponderServer.PAYLOAD_CONVERSION_ERROR_CODE, responseCaptor.getValue().getStatusCode().intValue()); assertNotNull(responseCaptor.getValue().getStatusMessage()); @@ -99,7 +111,7 @@ public void testResponderServerProcessUnexpectedPayload() throws Exception { public void testResponderServerProcessHandlerThrowException() throws Exception { String exceptionMessage = "Test exception message"; Exception error = new Exception(exceptionMessage); - ResponderServer.RequestHandler handler = (request, responder) -> { + ResponderServer.RequestHandler handler = (request, responderContext) -> { throw error; }; @@ -109,18 +121,24 @@ public void testResponderServerProcessHandlerThrowException() throws Exception { // simulate incoming request ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(RestPayload.class); + Message originalMessage = TestUtils.createMsbRequestMessageNoPayload(TOPIC); ResponderImpl responder = spy( - new ResponderImpl(messageTemplate, TestUtils.createMsbRequestMessageNoPayload(TOPIC), null, msbContext)); - responderServer.onResponder(responder); + new ResponderImpl(messageTemplate, originalMessage, msbContext)); + AcknowledgementHandler acknowledgeHandler = mock(AcknowledgementHandler.class); + ResponderContext responderContext = responderServer.createResponderContext(responder, acknowledgeHandler, originalMessage); + + responderServer.onResponder(responderContext); verify(responder).send(responseCaptor.capture()); + verify(acknowledgeHandler).confirmMessage(); + assertEquals(ResponderServer.INTERNAL_SERVER_ERROR_CODE, responseCaptor.getValue().getStatusCode().intValue()); assertEquals(exceptionMessage, responseCaptor.getValue().getStatusMessage()); } @Test public void testCreateResponderWithResponseTopic() { - ResponderServer.RequestHandler handler = (request, responder) -> { + ResponderServer.RequestHandler handler = (request, responderContext) -> { }; ChannelManager mockChannelManager = mock(ChannelManager.class); @@ -134,8 +152,9 @@ public void testCreateResponderWithResponseTopic() { .create(TOPIC, messageTemplate, msbContext1, handler, new TypeReference() {}); Message incomingMessage = TestUtils.createMsbRequestMessageNoPayload(TOPIC); - Responder responder = responderServer.createResponder(incomingMessage, null); - assertEquals(incomingMessage, responder.getOriginalMessage()); + Responder responder = responderServer.createResponder(incomingMessage); + ResponderContext responderContext = responderServer.createResponderContext(responder, null, incomingMessage); + assertEquals(incomingMessage, responderContext.getOriginalMessage()); responder.sendAck(1, 1); responder.send("response"); @@ -146,7 +165,7 @@ public void testCreateResponderWithResponseTopic() { @Test public void testCreateResponderNoResponseTopic() { - ResponderServer.RequestHandler handler = (request, responder) -> { + ResponderServer.RequestHandler handler = (request, responderContext) -> { }; ChannelManager mockChannelManager = mock(ChannelManager.class); @@ -158,8 +177,7 @@ public void testCreateResponderNoResponseTopic() { .create(TOPIC, messageTemplate, msbContext, handler, new TypeReference() {}); Message incomingMessage = TestUtils.createMsbBroadcastMessageNoPayload(TOPIC); - Responder responder = responderServer.createResponder(incomingMessage, null); - assertEquals(incomingMessage, responder.getOriginalMessage()); + Responder responder = responderServer.createResponder(incomingMessage); responder.sendAck(1, 1); responder.send("response"); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java b/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java index c3b8299d..48f5940b 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java @@ -3,6 +3,7 @@ import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.MsbContext; import io.github.tcdl.msb.api.MsbContextBuilder; +import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.examples.payload.Query; import io.github.tcdl.msb.examples.payload.Request; @@ -30,13 +31,14 @@ public void start(MsbContext msbContext) { MessageTemplate messageTemplate = new MessageTemplate().withTags("date-extractor"); final String namespace = "search:parsers:facets:v1"; - msbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, responder) -> { + msbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, responderContext) -> { Query query = request.getQuery(); String queryString = query.getQ(); String year = DateExtractorUtils.retrieveYear(queryString); if (year != null) { + Responder responder = responderContext.getResponder(); // send acknowledge responder.sendAck(500, null); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java b/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java index 534114c8..6ff5dcb4 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java @@ -6,6 +6,7 @@ import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Requester; import io.github.tcdl.msb.api.Responder; +import io.github.tcdl.msb.api.ResponderContext; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.examples.payload.Request; @@ -35,9 +36,11 @@ public static void main(String[] args) throws ScriptException, FileNotFoundExcep MessageTemplate messageTemplate = new MessageTemplate().withTags("facets-aggregator"); final String namespace = "search:aggregator:facets:v1"; - msbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (Request facetsRequest, Responder responder) -> { + msbContext.getObjectFactory().createResponderServer( + namespace, messageTemplate, (Request facetsRequest, ResponderContext responderContext) -> { String q = facetsRequest.getQuery().getQ(); + Responder responder = responderContext.getResponder(); if (q == null) { RestPayload responsePayload = new RestPayload.Builder() @@ -91,7 +94,7 @@ public static void main(String[] args) throws ScriptException, FileNotFoundExcep responder.send(responsePayload); }); - requester.publish(facetsRequest, responder.getOriginalMessage(), UUID.randomUUID().toString()); + requester.publish(facetsRequest, responderContext.getOriginalMessage(), UUID.randomUUID().toString()); } }, Request.class).listen(); } diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java b/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java index e0eff238..c82fb47c 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java @@ -5,6 +5,7 @@ import io.github.tcdl.msb.api.MsbContextBuilder; import io.github.tcdl.msb.api.ObjectFactory; import io.github.tcdl.msb.api.ResponderServer; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,11 +19,12 @@ public static void main(String[] args) { ObjectFactory objectFactory = msbContext.getObjectFactory(); MessageTemplate messageTemplate = new MessageTemplate().withTags("pong-static-tag"); - ResponderServer responderServer = objectFactory.createResponderServer("pingpong:namespace", messageTemplate, (request, responder) -> { + ResponderServer responderServer = objectFactory.createResponderServer("pingpong:namespace", messageTemplate, + (request, responderContext) -> { // Response handling logic LOG.info(String.format("Handling %s...", request)); - responder.send("PONG"); + responderContext.getResponder().send("PONG"); LOG.info("Response sent"); }, String.class); From 06591ee85b829152a573d0638945459301b4a39a Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Mon, 7 Dec 2015 00:00:52 +0200 Subject: [PATCH 016/226] Added MessageContext --- .../amqp/AmqpAcknowledgementHandler.java | 14 ++-- .../github/tcdl/msb/api/MessageContext.java | 18 +++++ .../io/github/tcdl/msb/api/Requester.java | 6 +- .../github/tcdl/msb/api/ResponderContext.java | 13 +--- .../github/tcdl/msb/collector/Collector.java | 19 +++-- .../github/tcdl/msb/events/EventHandlers.java | 20 ++--- .../tcdl/msb/impl/MessageContextImpl.java | 27 +++++++ .../github/tcdl/msb/impl/RequesterImpl.java | 8 +- .../tcdl/msb/collector/CollectorTest.java | 78 ++++++++++++------- .../monitor/aggregator/HeartbeatTaskTest.java | 4 +- 10 files changed, 136 insertions(+), 71 deletions(-) create mode 100644 core/src/main/java/io/github/tcdl/msb/api/MessageContext.java create mode 100644 core/src/main/java/io/github/tcdl/msb/impl/MessageContextImpl.java diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java index 7ce1e27c..09571d30 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java @@ -2,6 +2,8 @@ import io.github.tcdl.msb.api.AcknowledgementHandler; +import java.util.concurrent.atomic.AtomicBoolean; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,7 +25,7 @@ public class AmqpAcknowledgementHandler implements AcknowledgementHandler { final long deliveryTag; final boolean isRequeueRejectedMessages; - boolean isAcknowledgementSent = false; + final AtomicBoolean acknowledgementSent = new AtomicBoolean(false); public AmqpAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) { @@ -36,10 +38,9 @@ public AmqpAcknowledgementHandler(Channel channel, String consumerTag, long deli @Override public void confirmMessage() { - if (!isAcknowledgementSent) { + if (acknowledgementSent.compareAndSet(false, true)) { try { channel.basicAck(deliveryTag, false); - isAcknowledgementSent = true; } catch (Exception e) { LOG.error(String.format("[consumer tag: %s] Got exception when trying to confirm a message:", consumerTag), e); } @@ -50,10 +51,9 @@ public void confirmMessage() { @Override public void rejectMessage() { - if (!isAcknowledgementSent) { + if (acknowledgementSent.compareAndSet(false, true)) { try { channel.basicReject(deliveryTag, isRequeueRejectedMessages); - isAcknowledgementSent = true; } catch (Exception e) { LOG.error(String.format("[consumer tag: %s] Got exception when trying to reject a message:", consumerTag), e); } @@ -63,13 +63,13 @@ public void rejectMessage() { } public void autoConfirm() { - if (!isAcknowledgementSent) { + if (!acknowledgementSent.get()) { confirmMessage(); } } public void autoReject() { - if (!isAcknowledgementSent) { + if (!acknowledgementSent.get()) { rejectMessage(); } } diff --git a/core/src/main/java/io/github/tcdl/msb/api/MessageContext.java b/core/src/main/java/io/github/tcdl/msb/api/MessageContext.java new file mode 100644 index 00000000..dd66f51b --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/api/MessageContext.java @@ -0,0 +1,18 @@ +package io.github.tcdl.msb.api; + +import io.github.tcdl.msb.api.message.Message; + +/** + * Provides access to Message Context. + */ +public interface MessageContext { + /** + * @return AcknowledgementHandler for explicit confirm/reject incoming messages + */ + AcknowledgementHandler getAcknowledgementHandler(); + + /** + * @return original message to send a response to + */ + Message getOriginalMessage(); +} diff --git a/core/src/main/java/io/github/tcdl/msb/api/Requester.java b/core/src/main/java/io/github/tcdl/msb/api/Requester.java index fe4bcdaa..d5ba3e67 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/Requester.java +++ b/core/src/main/java/io/github/tcdl/msb/api/Requester.java @@ -68,7 +68,7 @@ public interface Requester { * @param acknowledgeHandler callback to be called * @return requester */ - Requester onAcknowledge(BiConsumer acknowledgeHandler); + Requester onAcknowledge(BiConsumer acknowledgeHandler); /** * Registers a callback to be called when response {@link Message} with payload part set of type {@literal T} is received. @@ -77,7 +77,7 @@ public interface Requester { * @return requester * @throws JsonConversionException if unable to convert payload to type {@literal T} */ - Requester onResponse(BiConsumer responseHandler); + Requester onResponse(BiConsumer responseHandler); /** * Registers a callback to be called when response {@link Message} with payload part set of is received. @@ -85,7 +85,7 @@ public interface Requester { * @param responseHandler callback to be called * @return requester */ - Requester onRawResponse(BiConsumer responseHandler); + Requester onRawResponse(BiConsumer responseHandler); /** * Registers a callback to be called when all expected responses for request message are processes or awaiting timeout for responses occurred. diff --git a/core/src/main/java/io/github/tcdl/msb/api/ResponderContext.java b/core/src/main/java/io/github/tcdl/msb/api/ResponderContext.java index 8568cef7..c7221db8 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/ResponderContext.java +++ b/core/src/main/java/io/github/tcdl/msb/api/ResponderContext.java @@ -1,25 +1,14 @@ package io.github.tcdl.msb.api; -import io.github.tcdl.msb.api.message.Message; /** * Provides access to Responder Context. */ -public interface ResponderContext { +public interface ResponderContext extends MessageContext { /** * @return Responder instance */ Responder getResponder(); - /** - * @return AcknowledgementHandler for explicit confirm/reject incoming messages - */ - AcknowledgementHandler getAcknowledgementHandler(); - - /** - * @return original message to send a response to - */ - Message getOriginalMessage(); - } diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index cc635700..320d0020 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -4,10 +4,12 @@ import static java.lang.Math.toIntExact; import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; +import io.github.tcdl.msb.api.MessageContext; import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.events.EventHandlers; +import io.github.tcdl.msb.impl.MessageContextImpl; import io.github.tcdl.msb.impl.MsbContextImpl; import io.github.tcdl.msb.support.Utils; @@ -59,9 +61,9 @@ public class Collector { private Clock clock; private Message requestMessage; - private Optional> onRawResponse = Optional.empty(); - private Optional> onResponse = Optional.empty(); - private Optional> onAcknowledge = Optional.empty(); + private Optional> onRawResponse = Optional.empty(); + private Optional> onResponse = Optional.empty(); + private Optional> onAcknowledge = Optional.empty(); private Optional> onEnd = Optional.empty(); private ScheduledFuture ackTimeoutFuture; @@ -126,19 +128,20 @@ public void handleMessage(Message incomingMessage, AcknowledgementHandler acknow LOG.debug("Received {}", incomingMessage); JsonNode rawPayload = incomingMessage.getRawPayload(); + MessageContext messageContext = createMessageContext(acknowledgeHandler, incomingMessage); if (Utils.isPayloadPresent(rawPayload)) { LOG.debug("Received Payload {}", rawPayload); payloadMessages.add(incomingMessage); - onRawResponse.ifPresent(handler -> handler.accept(incomingMessage, acknowledgeHandler)); + onRawResponse.ifPresent(handler -> handler.accept(incomingMessage, messageContext)); T payload = Utils.convert(rawPayload, payloadTypeReference, payloadMapper); - onResponse.ifPresent(handler -> handler.accept(payload, acknowledgeHandler)); + onResponse.ifPresent(handler -> handler.accept(payload, messageContext)); incResponsesRemaining(-1); } else { LOG.debug("Received {}", incomingMessage.getAck()); ackMessages.add(incomingMessage); - onAcknowledge.ifPresent(handler -> handler.accept(incomingMessage.getAck(), acknowledgeHandler)); + onAcknowledge.ifPresent(handler -> handler.accept(incomingMessage.getAck(), messageContext)); } processAck(incomingMessage.getAck()); @@ -155,6 +158,10 @@ public void handleMessage(Message incomingMessage, AcknowledgementHandler acknow end(); } + MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandler, Message originalMessage) { + return new MessageContextImpl(acknowledgementHandler, originalMessage); + } + protected void end() { LOG.debug("Stop response processing"); diff --git a/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java b/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java index 7e5865f3..bd0056d6 100644 --- a/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java +++ b/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java @@ -1,7 +1,7 @@ package io.github.tcdl.msb.events; -import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; +import io.github.tcdl.msb.api.MessageContext; import io.github.tcdl.msb.api.Requester; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.Message; @@ -13,9 +13,9 @@ */ public class EventHandlers { - private BiConsumer onAcknowledge = (acknowledge, ackHandler) -> {}; - private BiConsumer onResponse = (acknowledge, ackHandler) -> {}; - private BiConsumer onRawResponse = (acknowledge, ackHandler) -> {}; + private BiConsumer onAcknowledge = (acknowledge, msgContext) -> {}; + private BiConsumer onResponse = (acknowledge, msgContext) -> {}; + private BiConsumer onRawResponse = (acknowledge, msgContext) -> {}; private Callback onEnd = messages -> {}; /** @@ -23,7 +23,7 @@ public class EventHandlers { * * @return acknowledge callback */ - public BiConsumer onAcknowledge() { + public BiConsumer onAcknowledge() { return onAcknowledge; } @@ -33,7 +33,7 @@ public BiConsumer onAcknowledge() { * @param onAcknowledge callback * @return EventHandlers */ - public EventHandlers onAcknowledge(BiConsumer onAcknowledge) { + public EventHandlers onAcknowledge(BiConsumer onAcknowledge) { this.onAcknowledge = onAcknowledge; return this; } @@ -44,7 +44,7 @@ public EventHandlers onAcknowledge(BiConsumer onResponse() { + public BiConsumer onResponse() { return onResponse; } @@ -54,7 +54,7 @@ public BiConsumer onResponse() { * @param onResponse callback * @return EventHandlers */ - public EventHandlers onResponse(BiConsumer onResponse) { + public EventHandlers onResponse(BiConsumer onResponse) { this.onResponse = onResponse; return this; } @@ -65,7 +65,7 @@ public EventHandlers onResponse(BiConsumer onResponse * @return response callback * @return EventHandlers */ - public BiConsumer onRawResponse() { + public BiConsumer onRawResponse() { return onRawResponse; } @@ -75,7 +75,7 @@ public BiConsumer onRawResponse() { * @param onRawResponse callback * @return EventHandlers */ - public EventHandlers onRawResponse(BiConsumer onRawResponse) { + public EventHandlers onRawResponse(BiConsumer onRawResponse) { this.onRawResponse = onRawResponse; return this; } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/MessageContextImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/MessageContextImpl.java new file mode 100644 index 00000000..ef58414a --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/impl/MessageContextImpl.java @@ -0,0 +1,27 @@ +package io.github.tcdl.msb.impl; + +import io.github.tcdl.msb.api.AcknowledgementHandler; +import io.github.tcdl.msb.api.MessageContext; +import io.github.tcdl.msb.api.message.Message; + +public class MessageContextImpl implements MessageContext { + private final Message originalMessage; + private final AcknowledgementHandler acknowledgementHandler; + + public MessageContextImpl(AcknowledgementHandler acknowledgementHandler, Message originalMessage) { + super(); + this.acknowledgementHandler = acknowledgementHandler; + this.originalMessage = originalMessage; + } + + @Override + public AcknowledgementHandler getAcknowledgementHandler() { + return acknowledgementHandler; + } + + @Override + public Message getOriginalMessage() { + return originalMessage; + } + +} diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java index 2ee3c63c..d6312c56 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java @@ -1,8 +1,8 @@ package io.github.tcdl.msb.impl; import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; +import io.github.tcdl.msb.api.MessageContext; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Requester; @@ -126,7 +126,7 @@ private boolean isWaitForResponses() { * {@inheritDoc} */ @Override - public Requester onAcknowledge(BiConsumer acknowledgeHandler) { + public Requester onAcknowledge(BiConsumer acknowledgeHandler) { eventHandlers.onAcknowledge(acknowledgeHandler); return this; } @@ -135,7 +135,7 @@ public Requester onAcknowledge(BiConsumer onResponse(BiConsumer responseHandler) { + public Requester onResponse(BiConsumer responseHandler) { eventHandlers.onResponse(responseHandler); return this; } @@ -143,7 +143,7 @@ public Requester onResponse(BiConsumer responseHan /** * {@inheritDoc} */ - @Override public Requester onRawResponse(BiConsumer responseHandler) { + @Override public Requester onRawResponse(BiConsumer responseHandler) { eventHandlers.onRawResponse(responseHandler); return this; } diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java index be55c775..8e1dc810 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java @@ -17,6 +17,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; +import io.github.tcdl.msb.api.MessageContext; import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Acknowledge; @@ -77,6 +78,9 @@ public class CollectorTest { @Mock private CollectorManager collectorManagerMock; + + @Mock + private MessageContext messageContextMock; private MsbContextImpl msbContext; @@ -151,25 +155,33 @@ public void testHandleResponse() { String bodyText = "some body"; Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); @SuppressWarnings("unchecked") - BiConsumer onResponse = mock(BiConsumer.class); + BiConsumer onResponse = mock(BiConsumer.class); @SuppressWarnings("unchecked") - BiConsumer onRawResponse = mock(BiConsumer.class); + BiConsumer onRawResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); when(eventHandlers.onRawResponse()).thenReturn(onRawResponse); - Collector collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, + + AcknowledgementHandler acknowledgeHandler = mock(AcknowledgementHandler.class); + MessageContext messageContext = mock(MessageContext.class); + + Collector collector = new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() { - }); + }) { - AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); + MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandler, Message originalMessage) { + return messageContext; + } + }; // method under test - collector.handleMessage(responseMessage, ackHandler); + collector.handleMessage(responseMessage, acknowledgeHandler); RestPayload expectedPayload = new RestPayload.Builder() .withBody(bodyText) .build(); - verify(onRawResponse).accept(responseMessage, ackHandler); - verify(onResponse).accept(expectedPayload, ackHandler); + + verify(onRawResponse).accept(responseMessage, messageContext); + verify(onResponse).accept(expectedPayload, messageContext); verify(collectorManagerMock).unregisterCollector(collector); assertTrue(collector.getPayloadMessages().stream().anyMatch(message -> message.getId().equals(responseMessage.getId()))); assertFalse(collector.getAckMessages().contains(responseMessage)); @@ -180,9 +192,9 @@ public void testHandleResponseConversionFailed() { String bodyText = "some body"; Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); @SuppressWarnings("unchecked") - BiConsumer, AcknowledgementHandler> onResponse = mock(BiConsumer.class); + BiConsumer, MessageContext> onResponse = mock(BiConsumer.class); @SuppressWarnings("unchecked") - BiConsumer onRawResponse = mock(BiConsumer.class); + BiConsumer onRawResponse = mock(BiConsumer.class); @SuppressWarnings("unchecked") EventHandlers> eventHandlers = mock(EventHandlers.class); @@ -190,16 +202,22 @@ public void testHandleResponseConversionFailed() { when(eventHandlers.onRawResponse()).thenReturn(onRawResponse); TypeReference> payloadTypeReference = new TypeReference>() { }; - Collector> collector = new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, - payloadTypeReference); AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); - + MessageContext messageContext = mock(MessageContext.class); + + Collector> collector = new Collector>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, + payloadTypeReference) { + MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandler, Message originalMessage) { + return messageContext; + } + }; + // make sure that onRawResponse is called even if conversion of payload to custom type fails try { collector.handleMessage(responseMessage, ackHandler); } finally { - verify(onRawResponse).accept(responseMessage, ackHandler); + verify(onRawResponse).accept(responseMessage, messageContext); verify(onResponse, never()).accept(any(), any()); } } @@ -207,14 +225,14 @@ public void testHandleResponseConversionFailed() { @Test @SuppressWarnings({ "rawtypes", "unchecked" }) public void testHandleResponseReceivedAck() { - BiConsumer onAck = mock(BiConsumer.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Collector collector = createCollector(); AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); collector.handleMessage(responseMessageWithAck, ackHandler); - verify(onAck).accept(responseMessageWithAck.getAck(), ackHandler); + verify(onAck).accept(responseMessageWithAck.getAck(), messageContextMock); assertTrue(collector.getAckMessages().contains(responseMessageWithAck)); assertFalse(collector.getPayloadMessages().contains(responseMessageWithAck)); } @@ -247,7 +265,7 @@ public void testHandleResponseLastResponse() { when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout); when(requestOptionsMock.getWaitForResponses()).thenReturn(1); - BiConsumer onResponse = mock(BiConsumer.class); + BiConsumer onResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -259,7 +277,7 @@ public void testHandleResponseLastResponse() { RestPayload expectedPayload = new RestPayload.Builder() .withBody(bodyText) .build(); - verify(onResponse).accept(expectedPayload, ackHandler); + verify(onResponse).accept(expectedPayload, messageContextMock); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(responseTimeout), eq(collector)); verify(timeoutManagerMock, never()).enableAckTimeout(eq(0), eq(collector)); verify(onEnd).call(any()); @@ -278,7 +296,7 @@ public void testHandleResponseWaitForOneMoreResponse() { when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout); when(requestOptionsMock.getWaitForResponses()).thenReturn(2); - BiConsumer onResponse = mock(BiConsumer.class); + BiConsumer onResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -293,12 +311,12 @@ public void testHandleResponseWaitForOneMoreResponse() { AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class); //send first response collector.handleMessage(responseMessage, ackHandler); - verify(onResponse).accept(expectedPayload, ackHandler); + verify(onResponse).accept(expectedPayload, messageContextMock); verify(onEnd, never()).call(any()); //send last response collector.handleMessage(responseMessage, ackHandler); - verify(onResponse, times(2)).accept(expectedPayload, ackHandler); + verify(onResponse, times(2)).accept(expectedPayload, messageContextMock); verify(timeoutManagerMock, never()).enableResponseTimeout(eq(responseTimeout), eq(collector)); verify(timeoutManagerMock, never()).enableAckTimeout(eq(0), eq(collector)); verify(onEnd).call(any()); @@ -317,7 +335,7 @@ public void testHandleResponseNoResponsesRemainingButAwaitAck() { when(requestOptionsMock.getResponseTimeout()).thenReturn(0); when(requestOptionsMock.getWaitForResponses()).thenReturn(0); - BiConsumer onAck = mock(BiConsumer.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Callback onEnd = mock(Callback.class); @@ -347,7 +365,7 @@ public void testHandleResponseReceivedPayloadButAwaitAck() { when(requestOptionsMock.getResponseTimeout()).thenReturn(0); when(requestOptionsMock.getWaitForResponses()).thenReturn(1); - BiConsumer onAck = mock(BiConsumer.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -376,7 +394,7 @@ public void testHandleResponseNoResponsesRemainingAndWaitUntilAckBeforeNow() { when(requestOptionsMock.getResponseTimeout()).thenReturn(0); when(requestOptionsMock.getWaitForResponses()).thenReturn(0); - BiConsumer onAck = mock(BiConsumer.class); + BiConsumer onAck = mock(BiConsumer.class); when(eventHandlers.onAcknowledge()).thenReturn(onAck); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -536,7 +554,7 @@ public void testHandleResponseEnsureResponsesRemainingIsDecreased() { when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout); when(requestOptionsMock.getWaitForResponses()).thenReturn(responsesRemaining); - BiConsumer onResponse = mock(BiConsumer.class); + BiConsumer onResponse = mock(BiConsumer.class); when(eventHandlers.onResponse()).thenReturn(onResponse); Callback onEnd = mock(Callback.class); when(eventHandlers.onEnd()).thenReturn(onEnd); @@ -781,8 +799,14 @@ public void testWaitForAcks() { } private Collector createCollector() { - return new Collector<>(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() { - }); + return new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() { + }) { + MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandler, Message originalMessage) { + return messageContextMock; + } + + }; } + } \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java b/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java index 8e61e3cf..f1775c4d 100644 --- a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java +++ b/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java @@ -6,8 +6,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.Callback; +import io.github.tcdl.msb.api.MessageContext; import io.github.tcdl.msb.api.ObjectFactory; import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Requester; @@ -40,7 +40,7 @@ public class HeartbeatTaskTest { public void setUp() { when(mockObjectFactory.createRequester(anyString(), any(RequestOptions.class))).thenReturn(mockRequester); @SuppressWarnings("unchecked") - BiConsumer responseHandler = any(BiConsumer.class); + BiConsumer responseHandler = any(BiConsumer.class); when(mockRequester.onRawResponse(responseHandler)).thenReturn(mockRequester); @SuppressWarnings("unchecked") Callback endHandler = any(Callback.class); From bfa9dff3e09bbcf9a1caddfd183aad647b3a0896 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Mon, 7 Dec 2015 10:17:19 +0200 Subject: [PATCH 017/226] Added setAutoAcknowledgement() and discardMessage() methods. --- .../amqp/AmqpAcknowledgementHandler.java | 37 ++++++++++++++++--- .../java/io/github/tcdl/msb/Consumer.java | 4 +- .../tcdl/msb/api/AcknowledgementHandler.java | 5 +++ .../java/io/github/tcdl/msb/ConsumerTest.java | 22 ++++------- 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java index 09571d30..09ab0494 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java @@ -16,7 +16,7 @@ public class AmqpAcknowledgementHandler implements AcknowledgementHandler { - private static final String ACK_WAS_ALREADY_SENT = "Acknowledgement was already sent during message processing."; + private static final String ACK_WAS_ALREADY_SENT = "[consumer tag: %s] Acknowledgement was already sent during message processing."; private static final Logger LOG = LoggerFactory.getLogger(AmqpAcknowledgementHandler.class); @@ -26,6 +26,7 @@ public class AmqpAcknowledgementHandler implements AcknowledgementHandler { final boolean isRequeueRejectedMessages; final AtomicBoolean acknowledgementSent = new AtomicBoolean(false); + boolean autoAcknowledgement = true; public AmqpAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) { @@ -36,16 +37,25 @@ public AmqpAcknowledgementHandler(Channel channel, String consumerTag, long deli this.isRequeueRejectedMessages = isRequeueRejectedMessages; } + public boolean isAutoAcknowledgement() { + return autoAcknowledgement; + } + + public void setAutoAcknowledgement(boolean autoAcknowledgement) { + this.autoAcknowledgement = autoAcknowledgement; + } + @Override public void confirmMessage() { if (acknowledgementSent.compareAndSet(false, true)) { try { channel.basicAck(deliveryTag, false); + LOG.debug(String.format("[consumer tag: %s] A message was confirmed", consumerTag)); } catch (Exception e) { LOG.error(String.format("[consumer tag: %s] Got exception when trying to confirm a message:", consumerTag), e); } } else { - LOG.warn(ACK_WAS_ALREADY_SENT); + LOG.error(String.format(ACK_WAS_ALREADY_SENT, consumerTag)); } } @@ -54,23 +64,40 @@ public void rejectMessage() { if (acknowledgementSent.compareAndSet(false, true)) { try { channel.basicReject(deliveryTag, isRequeueRejectedMessages); + LOG.debug(String.format("[consumer tag: %s] A message was rejected", consumerTag)); } catch (Exception e) { LOG.error(String.format("[consumer tag: %s] Got exception when trying to reject a message:", consumerTag), e); } } else { - LOG.warn(ACK_WAS_ALREADY_SENT); + LOG.error(String.format(ACK_WAS_ALREADY_SENT, consumerTag)); } } + @Override + public void discardMessage() { + if (acknowledgementSent.compareAndSet(false, true)) { + try { + channel.basicReject(deliveryTag, false); + LOG.debug(String.format("[consumer tag: %s] A message was discarded", consumerTag)); + } catch (Exception e) { + LOG.error(String.format("[consumer tag: %s] Got exception when trying to discard a message:", consumerTag), e); + } + } else { + LOG.error(String.format(ACK_WAS_ALREADY_SENT, consumerTag)); + } + } + public void autoConfirm() { - if (!acknowledgementSent.get()) { + if (autoAcknowledgement && !acknowledgementSent.get()) { confirmMessage(); + LOG.debug(String.format("[consumer tag: %s] A message was automatically confirmed after message processing", consumerTag)); } } public void autoReject() { - if (!acknowledgementSent.get()) { + if (autoAcknowledgement && !acknowledgementSent.get()) { rejectMessage(); + LOG.debug(String.format("[consumer tag: %s] A message was automatically rejected due to error during message processing", consumerTag)); } } diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index 7bb85a32..6cd7a2fe 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -102,11 +102,11 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandler ackno messageHandler.handleMessage(message, acknowledgeHandler); } else { LOG.warn("Expired message: {}", jsonMessage); - acknowledgeHandler.rejectMessage(); + acknowledgeHandler.discardMessage(); } } catch (JsonConversionException | JsonSchemaValidationException e) { LOG.error("Unable to process consumed message {}", jsonMessage, e); - acknowledgeHandler.rejectMessage(); + acknowledgeHandler.discardMessage(); } } diff --git a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java index cf1b225c..3feaec95 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java +++ b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java @@ -18,4 +18,9 @@ public interface AcknowledgementHandler { */ void rejectMessage(); + /** + * Inform server that a message was rejected by consumer and can't be requeue + */ + void discardMessage(); + } diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index fa48127f..0b730a7c 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -45,6 +45,9 @@ public class ConsumerTest { @Mock private MessageHandler messageHandlerMock; + + @Mock + private AcknowledgementHandler acknowledgementHandler; private Clock clock = Clock.systemDefaultZone(); @@ -113,7 +116,7 @@ public void testValidMessageProcessedBySubscriber() throws JsonConversionExcepti public void testExceptionWhileMessageConvertingProcessedBySubscriber() throws JsonConversionException { Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage("{\"body\":\"fake message\"}", createAcknowledgementHandler()); + consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandler); verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); } @@ -151,7 +154,7 @@ public void testHandleRawMessageConsumeFromTopicValidateThrowException() { MsbConfig msbConf = TestUtils.createMsbConfigurations(); Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage("{\"body\":\"fake message\"}", createAcknowledgementHandler()); + consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandler); verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); // no processing } @@ -161,7 +164,7 @@ public void testHandleRawMessageConsumeFromServiceTopicValidateThrowException() MsbConfig msbConf = TestUtils.createMsbConfigurations(); Consumer consumer = new Consumer(adapterMock, service_topic, messageHandlerMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage("{\"body\":\"fake message\"}", createAcknowledgementHandler()); + consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandler); verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); // no processing } @@ -170,7 +173,7 @@ public void testHandleRawMessageConsumeFromTopicExpiredMessage() throws JsonConv Message expiredMessage = createExpiredMsbRequestMessageWithTopicTo(TOPIC); Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper), createAcknowledgementHandler()); + consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper), acknowledgementHandler); verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); } @@ -186,15 +189,4 @@ private Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) { .build(); } - private AcknowledgementHandler createAcknowledgementHandler() { - return new AcknowledgementHandler() { - @Override - public void confirmMessage() { - } - - @Override - public void rejectMessage() { - } - }; - } } \ No newline at end of file From 0b986d9101915ca8960306bdd0d8d608d3f055c8 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Mon, 7 Dec 2015 11:59:41 +0200 Subject: [PATCH 018/226] Updated AcknowledgementHandler interface --- .../tcdl/msb/api/AcknowledgementHandler.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java index 3feaec95..1d41aa6b 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java +++ b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java @@ -5,6 +5,31 @@ * */ public interface AcknowledgementHandler { + + /** + * Set autoAcknowledgement value. + * @param autoAcknowledgement + * If autoAcknowledgement is true: + * 1. A message can be confirmed/rejected by microservice developer in {@link ResponderServer.process(()} + * or {@link Requester.onAcknowledge()}, {@link Requester.onResponse}, {@link Requester.onRawResponse()} methods. + * 2. If a message is not confirmed/rejected during a message processing, + * acknowledgement will be automatically sent just after completion these methods by rules: + * - message confirmed if message processed successfully, + * - message declined if message has incorrect structure and can't be processed + * - message rejected with requeue if error happens during processing + * If autoAcknowledgement is false: + * microservice developer MUST explicitly confirm/reject a message. + * autoAcknowledgement must be set to false if a message processing need to be continued in another thread. In this case + * a message should be explicitly confirmed/rejected by microservice developer + * autoAcknowledgement is true b default. + */ + void setAutoAcknowledgement(boolean autoAcknowledgement); + + /** + * @return current autoAcknowledgement value + */ + boolean isAutoAcknowledgement(); + /** * Inform server that a message was confirmed by consumer. * Server should consider message acknowledged once delivered From 2dc70f9dc0bb00b4386521b66a8b4366d10cc9cd Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Mon, 7 Dec 2015 13:06:59 +0200 Subject: [PATCH 019/226] Refactoring. Renamed AcknowledgementHandler methods --- .../amqp/AmqpAcknowledgementHandler.java | 8 +++---- .../amqp/AmqpAcknowledgementHandlerTest.java | 23 +++++++++++-------- .../java/io/github/tcdl/msb/Consumer.java | 4 ++-- .../tcdl/msb/api/AcknowledgementHandler.java | 14 +++++------ 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java index 09ab0494..4fca2b82 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java @@ -60,13 +60,13 @@ public void confirmMessage() { } @Override - public void rejectMessage() { + public void retryMessage() { if (acknowledgementSent.compareAndSet(false, true)) { try { channel.basicReject(deliveryTag, isRequeueRejectedMessages); - LOG.debug(String.format("[consumer tag: %s] A message was rejected", consumerTag)); + LOG.debug(String.format("[consumer tag: %s] A message was rejected with requeue", consumerTag)); } catch (Exception e) { - LOG.error(String.format("[consumer tag: %s] Got exception when trying to reject a message:", consumerTag), e); + LOG.error(String.format("[consumer tag: %s] Got exception when trying to reject with requeue a message:", consumerTag), e); } } else { LOG.error(String.format(ACK_WAS_ALREADY_SENT, consumerTag)); @@ -74,7 +74,7 @@ public void rejectMessage() { } @Override - public void discardMessage() { + public void rejectMessage() { if (acknowledgementSent.compareAndSet(false, true)) { try { channel.basicReject(deliveryTag, false); diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java index a53c8962..a036f08f 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java @@ -1,15 +1,18 @@ package io.github.tcdl.msb.adapters.amqp; -import com.rabbitmq.client.Channel; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.stream.IntStream; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import java.util.stream.IntStream; - -import static org.mockito.Mockito.*; +import com.rabbitmq.client.Channel; @RunWith(MockitoJUnitRunner.class) public class AmqpAcknowledgementHandlerTest { @@ -36,20 +39,20 @@ public void testMessageConfirmed() throws Exception { @Test public void testMessageRejectedWithRequeue() throws Exception { - handler.rejectMessage(); + handler.retryMessage(); verifyRejectedOnce(true); } @Test public void testMessageRejectedWithoutRequeue() throws Exception { handler = getHandler(false); - handler.rejectMessage(); + handler.retryMessage(); verifyRejectedOnce(false); } @Test public void testOnlyFirstRejectInvoked() throws Exception { - handler.rejectMessage(); + handler.retryMessage(); verifyRejectedOnce(); submitMultipleConfirmRejectRequests(); verifyRejectedOnce(); @@ -97,7 +100,7 @@ public void testAutoRejectIgnoredWhenConfirmedByClient() throws Exception { @Test public void testAutoConfirmIgnoredWhenRejectedByClient() throws Exception { - handler.rejectMessage(); + handler.retryMessage(); verifyRejectedOnce(); handler.autoConfirm(); verifyRejectedOnce(); @@ -105,7 +108,7 @@ public void testAutoConfirmIgnoredWhenRejectedByClient() throws Exception { @Test public void testAutoRejectIgnoredWhenRejectedByClient() throws Exception { - handler.rejectMessage(); + handler.retryMessage(); verifyRejectedOnce(); handler.autoReject(); verifyRejectedOnce(); @@ -128,7 +131,7 @@ private void verifyRejectedOnce(boolean isRequeue) throws Exception { private void submitMultipleConfirmRejectRequests() { IntStream.range(0, 5).forEach((i) -> { handler.confirmMessage(); - handler.rejectMessage(); + handler.retryMessage(); }); } diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index 6cd7a2fe..7bb85a32 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -102,11 +102,11 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandler ackno messageHandler.handleMessage(message, acknowledgeHandler); } else { LOG.warn("Expired message: {}", jsonMessage); - acknowledgeHandler.discardMessage(); + acknowledgeHandler.rejectMessage(); } } catch (JsonConversionException | JsonSchemaValidationException e) { LOG.error("Unable to process consumed message {}", jsonMessage, e); - acknowledgeHandler.discardMessage(); + acknowledgeHandler.rejectMessage(); } } diff --git a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java index 1d41aa6b..d6bbd7c4 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java +++ b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java @@ -10,8 +10,8 @@ public interface AcknowledgementHandler { * Set autoAcknowledgement value. * @param autoAcknowledgement * If autoAcknowledgement is true: - * 1. A message can be confirmed/rejected by microservice developer in {@link ResponderServer.process(()} - * or {@link Requester.onAcknowledge()}, {@link Requester.onResponse}, {@link Requester.onRawResponse()} methods. + * 1. A message can be confirmed/rejected by microservice developer in ResponderServer.process(() (see {@link ResponderServer}) + * or Requester.onAcknowledge(), Requester.onResponse, Requester.onRawResponse() (see {@link Requester}) methods. * 2. If a message is not confirmed/rejected during a message processing, * acknowledgement will be automatically sent just after completion these methods by rules: * - message confirmed if message processed successfully, @@ -37,15 +37,13 @@ public interface AcknowledgementHandler { void confirmMessage(); /** - * Inform server that a message was rejected by consumer. - * AMQP Server may requeue message or delete it from queue depending on the - * requeueRejectedMessages configuration option + * Inform server that a message was rejected with requeue by consumer. */ - void rejectMessage(); + void retryMessage(); /** - * Inform server that a message was rejected by consumer and can't be requeue + * Inform server that a message was rejected by consumer without requeue */ - void discardMessage(); + void rejectMessage(); } From ee7785521f364ca876a53e241e04c9f24726908a Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 7 Dec 2015 14:57:20 +0200 Subject: [PATCH 020/226] WEB-16081 amqp ack - unit testing and misc. fixes --- .../amqp/AmqpAcknowledgementHandler.java | 78 +++++--- .../adapters/amqp/AmqpMessageConsumer.java | 2 +- .../amqp/AmqpMessageProcessingTask.java | 2 +- .../amqp/AmqpAcknowledgementHandlerTest.java | 168 ++++++++++++++---- .../amqp/AmqpMessageProcessingTaskTest.java | 2 +- 5 files changed, 186 insertions(+), 66 deletions(-) diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java index 4fca2b82..44f1483b 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java @@ -1,8 +1,10 @@ package io.github.tcdl.msb.adapters.amqp; +import com.rabbitmq.client.AMQP; import io.github.tcdl.msb.api.AcknowledgementHandler; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,18 +25,18 @@ public class AmqpAcknowledgementHandler implements AcknowledgementHandler { final Channel channel; final String consumerTag; final long deliveryTag; - final boolean isRequeueRejectedMessages; + final boolean isMessageRedelivered; final AtomicBoolean acknowledgementSent = new AtomicBoolean(false); boolean autoAcknowledgement = true; public AmqpAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, - boolean isRequeueRejectedMessages) { + boolean isMessageRedelivered) { super(); this.channel = channel; this.consumerTag = consumerTag; this.deliveryTag = deliveryTag; - this.isRequeueRejectedMessages = isRequeueRejectedMessages; + this.isMessageRedelivered = isMessageRedelivered; } public boolean isAutoAcknowledgement() { @@ -47,40 +49,39 @@ public void setAutoAcknowledgement(boolean autoAcknowledgement) { @Override public void confirmMessage() { - if (acknowledgementSent.compareAndSet(false, true)) { - try { - channel.basicAck(deliveryTag, false); - LOG.debug(String.format("[consumer tag: %s] A message was confirmed", consumerTag)); - } catch (Exception e) { - LOG.error(String.format("[consumer tag: %s] Got exception when trying to confirm a message:", consumerTag), e); - } - } else { - LOG.error(String.format(ACK_WAS_ALREADY_SENT, consumerTag)); - } + executeAck("confirm", () -> { + channel.basicAck(deliveryTag, false); + LOG.debug(String.format("[consumer tag: %s] A message was confirmed", consumerTag)); + }); } @Override public void retryMessage() { - if (acknowledgementSent.compareAndSet(false, true)) { - try { - channel.basicReject(deliveryTag, isRequeueRejectedMessages); + executeAck("requeue", () -> { + if(!isMessageRedelivered) { + channel.basicReject(deliveryTag, true); LOG.debug(String.format("[consumer tag: %s] A message was rejected with requeue", consumerTag)); - } catch (Exception e) { - LOG.error(String.format("[consumer tag: %s] Got exception when trying to reject with requeue a message:", consumerTag), e); + } else { + channel.basicReject(deliveryTag, false); + LOG.warn(String.format("[consumer tag: %s] Can't requeue message because it already was redelivered once, discarding it instead", consumerTag)); } - } else { - LOG.error(String.format(ACK_WAS_ALREADY_SENT, consumerTag)); - } + }); } @Override public void rejectMessage() { + executeAck("reject", () -> { + channel.basicReject(deliveryTag, false); + LOG.debug(String.format("[consumer tag: %s] A message was discarded", consumerTag)); + }); + } + + private void executeAck(String actionName, AckAction ackAction) { if (acknowledgementSent.compareAndSet(false, true)) { try { - channel.basicReject(deliveryTag, false); - LOG.debug(String.format("[consumer tag: %s] A message was discarded", consumerTag)); + ackAction.perform(); } catch (Exception e) { - LOG.error(String.format("[consumer tag: %s] Got exception when trying to discard a message:", consumerTag), e); + LOG.error(String.format("[consumer tag: %s] Got exception when trying to %s a message:", consumerTag, actionName), e); } } else { LOG.error(String.format(ACK_WAS_ALREADY_SENT, consumerTag)); @@ -88,17 +89,40 @@ public void rejectMessage() { } public void autoConfirm() { - if (autoAcknowledgement && !acknowledgementSent.get()) { + executeAutoAck(() -> { confirmMessage(); LOG.debug(String.format("[consumer tag: %s] A message was automatically confirmed after message processing", consumerTag)); - } + }); } public void autoReject() { - if (autoAcknowledgement && !acknowledgementSent.get()) { + executeAutoAck(() -> { rejectMessage(); LOG.debug(String.format("[consumer tag: %s] A message was automatically rejected due to error during message processing", consumerTag)); + }); + } + + public void autoRetry() { + executeAutoAck(() -> { + retryMessage(); + LOG.debug(String.format("[consumer tag: %s] A message was automatically rejected (with a requeue attempt) due to error during message processing", consumerTag)); + }); + } + + private void executeAutoAck(AutoAckAction ackAction) { + if (autoAcknowledgement && !acknowledgementSent.get()) { + ackAction.perform(); } } + @FunctionalInterface + private interface AckAction { + void perform() throws Exception; + } + + @FunctionalInterface + private interface AutoAckAction { + void perform(); + } + } diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java index 19ed8377..a614fa81 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java @@ -45,7 +45,7 @@ public AmqpMessageConsumer(Channel channel, ExecutorService consumerThreadPool, public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { long deliveryTag = envelope.getDeliveryTag(); AmqpAcknowledgementHandler ackHandler = createAcknowledgementHandler( - getChannel(), consumerTag, deliveryTag, amqpBrokerConfig.isRequeueRejectedMessages()); + getChannel(), consumerTag, deliveryTag, envelope.isRedeliver()); try { Charset charset = amqpBrokerConfig.getCharset(); diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java index d2e42eef..2751ad34 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java @@ -38,7 +38,7 @@ public void run() { ackHandler.autoConfirm(); } catch (Exception e) { LOG.error(String.format("[consumer tag: %s] Failed to process message %s", consumerTag, body), e); - ackHandler.autoReject(); + ackHandler.autoRetry(); } } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java index a036f08f..11148d66 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java @@ -1,8 +1,8 @@ package io.github.tcdl.msb.adapters.amqp; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + import java.util.stream.IntStream; @@ -24,107 +24,201 @@ public class AmqpAcknowledgementHandlerTest { private long deliveryTag = 123123123; - private boolean isRequeueByDefault = true; @Before public void setUp() { - handler = getHandler(isRequeueByDefault); + handler = getHandler(false); } @Test public void testMessageConfirmed() throws Exception { handler.confirmMessage(); - verifyConfirmedOnce(); + verifySingleConfirm(); } @Test - public void testMessageRejectedWithRequeue() throws Exception { + public void testMessageRejected() throws Exception { + handler.rejectMessage(); + verifySingleReject(); + } + + @Test + public void testMessageRequeued() throws Exception { handler.retryMessage(); - verifyRejectedOnce(true); + verifySingleRetry(); } @Test - public void testMessageRejectedWithoutRequeue() throws Exception { - handler = getHandler(false); + public void testMessageConfirmedWhenAutoAcknowledgementDisabled() throws Exception { + handler.setAutoAcknowledgement(false); + handler.confirmMessage(); + verifySingleConfirm(); + } + + @Test + public void testMessageRejectedWhenAutoAcknowledgementDisabled() throws Exception { + handler.setAutoAcknowledgement(false); + handler.rejectMessage(); + verifySingleReject(); + } + + @Test + public void testMessageRequeuedWhenAutoAcknowledgementDisabled() throws Exception { + handler.setAutoAcknowledgement(false); + handler.retryMessage(); + verifySingleRetry(); + } + + @Test + public void testAutoAcknowledgementChanged() throws Exception { + assertTrue(handler.isAutoAcknowledgement()); + handler.setAutoAcknowledgement(false); + assertFalse(handler.isAutoAcknowledgement()); + } + + @Test + public void testRedeliveredMessageRejected() throws Exception { + handler = getHandler(true); + handler.rejectMessage(); + verifySingleReject(); + } + + @Test + public void testRedeliveredMessageRejectedInsteadOfRetry() throws Exception { + handler = getHandler(true); handler.retryMessage(); - verifyRejectedOnce(false); + verifySingleReject(); + } + + @Test + public void testRedeliveredMessageConfirmed() throws Exception { + handler = getHandler(true); + handler.confirmMessage(); + verifySingleConfirm(); } @Test public void testOnlyFirstRejectInvoked() throws Exception { + handler.rejectMessage(); + verifySingleReject(); + submitMultipleConfirmRejectRequests(); + verifySingleReject(); + } + + @Test + public void testOnlyFirstRetryInvoked() throws Exception { handler.retryMessage(); - verifyRejectedOnce(); + verifySingleRetry(); submitMultipleConfirmRejectRequests(); - verifyRejectedOnce(); + verifySingleRetry(); } @Test public void testOnlyFirstConfirmInvoked() throws Exception { handler.confirmMessage(); - verifyConfirmedOnce(); + verifySingleConfirm(); submitMultipleConfirmRejectRequests(); - verifyConfirmedOnce(); + verifySingleConfirm(); } @Test public void testAutoConfirmConfirmsMessageOnce() throws Exception { handler.autoConfirm(); - verifyConfirmedOnce(); + verifySingleConfirm(); submitMultipleAutoConfirmAutoRejectRequests(); - verifyConfirmedOnce(); + verifySingleConfirm(); } @Test public void testAutoRejectRejectsMessageOnce() throws Exception { handler.autoReject(); - verifyRejectedOnce(); + verifySingleReject(); + submitMultipleAutoConfirmAutoRejectRequests(); + verifySingleReject(); + } + + @Test + public void testAutoRetryRequeueMessageOnce() throws Exception { + handler.autoRetry(); + verifySingleRetry(); submitMultipleAutoConfirmAutoRejectRequests(); - verifyRejectedOnce(); + verifySingleRetry(); + } + + @Test + public void testAutoConfirmIgnoredWhenAutoAcknowledgementDisabled() throws Exception { + handler.setAutoAcknowledgement(false); + handler.autoConfirm(); + verifyNoMoreInteractions(mockChannel); + } + + @Test + public void testAutoRejectIgnoredWhenAutoAcknowledgementDisabled() throws Exception { + handler.setAutoAcknowledgement(false); + handler.autoReject(); + verifyNoMoreInteractions(mockChannel); + } + + @Test + public void testAutoRetryIgnoredWhenAutoAcknowledgementDisabled() throws Exception { + handler.setAutoAcknowledgement(false); + handler.autoRetry(); + verifyNoMoreInteractions(mockChannel); + } + + @Test + public void testAutoRetryRejectRedeliveredMessageOnce() throws Exception { + handler = getHandler(true); + handler.autoRetry(); + verifySingleReject(); + submitMultipleAutoConfirmAutoRejectRequests(); + verifySingleReject(); } @Test public void testAutoConfirmIgnoredWhenConfirmedByClient() throws Exception { handler.confirmMessage(); - verifyConfirmedOnce(); + verifySingleConfirm(); handler.autoConfirm(); - verifyConfirmedOnce(); + verifySingleConfirm(); } @Test public void testAutoRejectIgnoredWhenConfirmedByClient() throws Exception { handler.confirmMessage(); - verifyConfirmedOnce(); + verifySingleConfirm(); handler.autoReject(); - verifyConfirmedOnce(); + verifySingleConfirm(); } @Test - public void testAutoConfirmIgnoredWhenRejectedByClient() throws Exception { + public void testAutoConfirmIgnoredWhenRetryByClient() throws Exception { handler.retryMessage(); - verifyRejectedOnce(); + verifySingleRetry(); handler.autoConfirm(); - verifyRejectedOnce(); + verifySingleRetry(); } @Test public void testAutoRejectIgnoredWhenRejectedByClient() throws Exception { - handler.retryMessage(); - verifyRejectedOnce(); + handler.rejectMessage(); + verifySingleReject(); handler.autoReject(); - verifyRejectedOnce(); + verifySingleReject(); } - private void verifyConfirmedOnce() throws Exception { + private void verifySingleConfirm() throws Exception { verify(mockChannel, times(1)).basicAck(deliveryTag, false); verifyNoMoreInteractions(mockChannel); } - private void verifyRejectedOnce() throws Exception { - verifyRejectedOnce(isRequeueByDefault); + private void verifySingleRetry() throws Exception { + verify(mockChannel, times(1)).basicReject(deliveryTag, true); + verifyNoMoreInteractions(mockChannel); } - private void verifyRejectedOnce(boolean isRequeue) throws Exception { - verify(mockChannel, times(1)).basicReject(deliveryTag, isRequeue); + private void verifySingleReject() throws Exception { + verify(mockChannel, times(1)).basicReject(deliveryTag, false); verifyNoMoreInteractions(mockChannel); } @@ -132,18 +226,20 @@ private void submitMultipleConfirmRejectRequests() { IntStream.range(0, 5).forEach((i) -> { handler.confirmMessage(); handler.retryMessage(); + handler.rejectMessage(); }); } private void submitMultipleAutoConfirmAutoRejectRequests() { IntStream.range(0, 5).forEach((i) -> { handler.autoReject(); + handler.autoRetry(); handler.autoConfirm(); }); } - private AmqpAcknowledgementHandler getHandler(boolean isRequeue) { - return new AmqpAcknowledgementHandler(mockChannel, "any", deliveryTag, isRequeue); + private AmqpAcknowledgementHandler getHandler(boolean isMessageRedelivered) { + return new AmqpAcknowledgementHandler(mockChannel, "any", deliveryTag, isMessageRedelivered); } } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java index 82c46f86..7b0838e6 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java @@ -55,7 +55,7 @@ public void testExceptionDuringProcessing() { // Verify that AMQP ack has not been sent verifyNoMoreInteractions(mockChannel); - verify(mockAcknowledgementHandler, times(1)).autoReject(); + verify(mockAcknowledgementHandler, times(1)).autoRetry(); verifyNoMoreInteractions(mockAcknowledgementHandler); } catch (Exception e) { fail(); From 6fffc210305b9ce960dd8d74b5907007efd13659 Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 7 Dec 2015 15:16:10 +0200 Subject: [PATCH 021/226] WEB-16081 amqp ack - testing and misc. fixes --- .../bdd/steps/RequesterResponderSteps.java | 80 ++++++++++++++----- .../scenarios/requester_responder.story | 66 +++++++++++++-- .../amqp/AmqpAcknowledgementHandler.java | 2 +- 3 files changed, 122 insertions(+), 26 deletions(-) diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java index 9eb331fe..99768b28 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java @@ -32,7 +32,12 @@ public class RequesterResponderSteps extends MsbSteps { private CompletableFuture> receivedResponseFuture; private int countRequestsReceived = 0; private Optional nextRequestAckType = Optional.empty(); + private Optional defaultRequestsAckType = Optional.empty(); + private boolean isResponseInNewThread = false; + public Optional getDefaultRequestsAckType() { + return defaultRequestsAckType; + } // responder steps @Given("responder server listens on namespace $namespace") @@ -45,28 +50,48 @@ public void createResponderServer(String namespace) { public void createResponderServer(String contextName, String namespace) { ObjectMapper mapper = helper.getPayloadMapper(contextName); countRequestsReceived = 0; + nextRequestAckType = Optional.empty(); + defaultRequestsAckType = Optional.empty(); + isResponseInNewThread = false; helper.createResponderServer(contextName, namespace, (request, responderContext) -> { if (responseBody != null) { countRequestsReceived++; - boolean isSendResponse = true; - - switch (nextRequestAckType.orElseGet(()->"auto")) { - case "confirm": - responderContext.getAcknowledgementHandler().confirmMessage(); - break; - case "reject": - responderContext.getAcknowledgementHandler().rejectMessage(); - isSendResponse = false; - break; - } - - nextRequestAckType = Optional.empty(); - if(isSendResponse) { - RestPayload payload = new RestPayload.Builder() - .withBody(Utils.fromJson(responseBody, Map.class, mapper)) - .build(); - responderContext.getResponder().send(payload); + Runnable responseActions = () -> { + boolean isSendResponse = true; + String ackType = nextRequestAckType.orElseGet( + () -> defaultRequestsAckType.orElseGet( + () -> "auto")); + + switch (ackType) { + case "confirm": + responderContext.getAcknowledgementHandler().confirmMessage(); + break; + case "reject": + responderContext.getAcknowledgementHandler().rejectMessage(); + isSendResponse = false; + break; + case "retry": + responderContext.getAcknowledgementHandler().retryMessage(); + isSendResponse = false; + break; + } + + nextRequestAckType = Optional.empty(); + + if (isSendResponse) { + RestPayload payload = new RestPayload.Builder() + .withBody(Utils.fromJson(responseBody, Map.class, mapper)) + .build(); + responderContext.getResponder().send(payload); + } + }; + + if(isResponseInNewThread) { + responderContext.getAcknowledgementHandler().setAutoAcknowledgement(false); + new Thread(responseActions).run(); + } else { + responseActions.run(); } } }).listen(); @@ -75,7 +100,16 @@ public void createResponderServer(String contextName, String namespace) { @Given("responder server will $nextRequestAckType next request") public void setNextRequestAckType(String nextRequestAckType) throws Exception { this.nextRequestAckType = Optional.of(nextRequestAckType); + } + + @Given("responder server will $allRequestsAckType all requests") + public void setDefaultRequestsAckType(String allRequestsAckType) throws Exception { + this.defaultRequestsAckType = Optional.of(allRequestsAckType); + } + @Given("responder server will send acknowledge and response from a new thread") + public void setResponseInNewThread() throws Exception { + isResponseInNewThread = true; } @Given("responder server responds with '$body'") @@ -140,6 +174,16 @@ public void waitForResponse(long timeout) throws Exception { Assert.assertNotNull("Response received is null", receivedResponse); } + @Then("requester does not get a response in $timeout ms") + public void waitForNoResponse(long timeout) throws Exception { + try { + receivedResponse = receivedResponseFuture.get(timeout, TimeUnit.MILLISECONDS); + } catch (TimeoutException timeoutException) { + //ok + } + Assert.assertNull("Unexpected response received", receivedResponse); + } + @Then("response equals $table") public void responseEquals(ExamplesTable table) throws Exception { Map expected = table.getRow(0); diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story index c701d3a6..b7830a35 100644 --- a/acceptance/src/test/resources/scenarios/requester_responder.story +++ b/acceptance/src/test/resources/scenarios/requester_responder.story @@ -17,11 +17,27 @@ And response equals |result| |hello jbehave| -Scenario: Responder confirms a message manually + +Scenario: Responder ask to retry a first message delivery so it will be redelivered + +Given responder server responds with '{"result": "hello jbehave - manual retry"}' +And responder server listens on namespace test:jbehave +And responder server will confirm all requests +And responder server will retry next request +And requester sends requests to namespace test:jbehave +When requester sends a request +Then requester gets response in 5000 ms +And responder requests received count equals 2 +And response equals +|result| +|hello jbehave - manual retry| + + +Scenario: Responder confirms all incoming messages manually Given responder server responds with '{"result": "hello jbehave - manual confirm"}' And responder server listens on namespace test:jbehave -And responder server will confirm next request +And responder server will confirm all requests And requester sends requests to namespace test:jbehave When requester sends a request Then requester gets response in 5000 ms @@ -30,17 +46,53 @@ And response equals |result| |hello jbehave - manual confirm| -Scenario: Responder rejects a first message delivery so it will be redelivered -Given responder server responds with '{"result": "hello jbehave - manual reject"}' +Scenario: Responder retrys all incoming messages manually + +Given responder server responds with '{"result": "hello jbehave - constant retry"}' And responder server listens on namespace test:jbehave -And responder server will reject next request +And responder server will retry all requests And requester sends requests to namespace test:jbehave When requester sends a request -Then requester gets response in 5000 ms +Then requester does not get a response in 1000 ms And responder requests received count equals 2 + + +Scenario: Responder rejctes all incoming messages manually + +Given responder server responds with '{"result": "hello jbehave - constant reject"}' +And responder server listens on namespace test:jbehave +And responder server will reject all requests +And requester sends requests to namespace test:jbehave +When requester sends a request +Then requester does not get a response in 1000 ms +And responder requests received count equals 1 + + +Scenario: Responder confirms all incoming messages manually in a different thread + +Given responder server responds with '{"result": "hello jbehave - manual confirm"}' +And responder server listens on namespace test:jbehave +And responder server will confirm all requests +And responder server will send acknowledge and response from a new thread +And requester sends requests to namespace test:jbehave +When requester sends a request +Then requester gets response in 5000 ms +And responder requests received count equals 1 And response equals |result| -|hello jbehave - manual reject| +|hello jbehave - manual confirm| + + +Scenario: Responder retrys all incoming messages manually in a different thread + +Given responder server responds with '{"result": "hello jbehave - constant retry"}' +And responder server listens on namespace test:jbehave +And responder server will retry all requests +And responder server will send acknowledge and response from a new thread +And requester sends requests to namespace test:jbehave +When requester sends a request +Then requester does not get a response in 1000 ms +And responder requests received count equals 2 diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java index 44f1483b..6a39bd2c 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java @@ -28,7 +28,7 @@ public class AmqpAcknowledgementHandler implements AcknowledgementHandler { final boolean isMessageRedelivered; final AtomicBoolean acknowledgementSent = new AtomicBoolean(false); - boolean autoAcknowledgement = true; + volatile boolean autoAcknowledgement = true; public AmqpAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isMessageRedelivered) { From 7fd0dfd64824a770137bfd2c3701609eda913169 Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 7 Dec 2015 18:26:29 +0200 Subject: [PATCH 022/226] WEB-16081 ack - refactoring --- .../amqp/AmqpAcknowledgementAdapter.java | 34 +++++ .../amqp/AmqpAcknowledgementHandler.java | 128 ------------------ .../adapters/amqp/AmqpMessageConsumer.java | 12 +- .../amqp/AmqpMessageProcessingTask.java | 8 +- .../amqp/AmqpAcknowledgementHandlerTest.java | 29 ++-- .../amqp/AmqpMessageConsumerTest.java | 5 +- .../amqp/AmqpMessageProcessingTaskTest.java | 3 +- .../tcdl/msb/cli/CliMessageHandler.java | 4 +- .../java/io/github/tcdl/msb/Consumer.java | 52 ++++--- .../acknowledge/AcknowledgementAdapter.java | 25 ++++ .../AcknowledgementHandlerImpl.java | 121 +++++++++++++++++ .../AcknowledgementHandlerInternal.java | 24 ++++ .../tcdl/msb/adapters/ConsumerAdapter.java | 4 +- .../java/io/github/tcdl/msb/ConsumerTest.java | 12 +- 14 files changed, 278 insertions(+), 183 deletions(-) create mode 100644 amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementAdapter.java delete mode 100644 amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java create mode 100644 core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementAdapter.java create mode 100644 core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java create mode 100644 core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerInternal.java diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementAdapter.java new file mode 100644 index 00000000..75a3a2ee --- /dev/null +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementAdapter.java @@ -0,0 +1,34 @@ +package io.github.tcdl.msb.adapters.amqp; + +import com.rabbitmq.client.Channel; +import io.github.tcdl.msb.acknowledge.AcknowledgementAdapter; + +/** + * AMQP acknowledgement implementation. + */ +public class AmqpAcknowledgementAdapter implements AcknowledgementAdapter { + final Channel channel; + final String identifier; + final long deliveryTag; + + public AmqpAcknowledgementAdapter(Channel channel, String identifier, long deliveryTag) { + this.channel = channel; + this.identifier = identifier; + this.deliveryTag = deliveryTag; + } + + @Override + public void confirm() throws Exception { + channel.basicAck(deliveryTag, false); + } + + @Override + public void reject() throws Exception { + channel.basicReject(deliveryTag, false); + } + + @Override + public void retry() throws Exception { + channel.basicReject(deliveryTag, true); + } +} diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java deleted file mode 100644 index 6a39bd2c..00000000 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandler.java +++ /dev/null @@ -1,128 +0,0 @@ -package io.github.tcdl.msb.adapters.amqp; - -import com.rabbitmq.client.AMQP; -import io.github.tcdl.msb.api.AcknowledgementHandler; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.rabbitmq.client.Channel; - -/** - * {@link AmqpAcknowledgementHandler} provides acknowledgement for AMQP broker. - * Used from {@link AmqpMessageConsumer}. - */ - -public class AmqpAcknowledgementHandler implements AcknowledgementHandler { - - private static final String ACK_WAS_ALREADY_SENT = "[consumer tag: %s] Acknowledgement was already sent during message processing."; - - private static final Logger LOG = LoggerFactory.getLogger(AmqpAcknowledgementHandler.class); - - final Channel channel; - final String consumerTag; - final long deliveryTag; - final boolean isMessageRedelivered; - - final AtomicBoolean acknowledgementSent = new AtomicBoolean(false); - volatile boolean autoAcknowledgement = true; - - public AmqpAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, - boolean isMessageRedelivered) { - super(); - this.channel = channel; - this.consumerTag = consumerTag; - this.deliveryTag = deliveryTag; - this.isMessageRedelivered = isMessageRedelivered; - } - - public boolean isAutoAcknowledgement() { - return autoAcknowledgement; - } - - public void setAutoAcknowledgement(boolean autoAcknowledgement) { - this.autoAcknowledgement = autoAcknowledgement; - } - - @Override - public void confirmMessage() { - executeAck("confirm", () -> { - channel.basicAck(deliveryTag, false); - LOG.debug(String.format("[consumer tag: %s] A message was confirmed", consumerTag)); - }); - } - - @Override - public void retryMessage() { - executeAck("requeue", () -> { - if(!isMessageRedelivered) { - channel.basicReject(deliveryTag, true); - LOG.debug(String.format("[consumer tag: %s] A message was rejected with requeue", consumerTag)); - } else { - channel.basicReject(deliveryTag, false); - LOG.warn(String.format("[consumer tag: %s] Can't requeue message because it already was redelivered once, discarding it instead", consumerTag)); - } - }); - } - - @Override - public void rejectMessage() { - executeAck("reject", () -> { - channel.basicReject(deliveryTag, false); - LOG.debug(String.format("[consumer tag: %s] A message was discarded", consumerTag)); - }); - } - - private void executeAck(String actionName, AckAction ackAction) { - if (acknowledgementSent.compareAndSet(false, true)) { - try { - ackAction.perform(); - } catch (Exception e) { - LOG.error(String.format("[consumer tag: %s] Got exception when trying to %s a message:", consumerTag, actionName), e); - } - } else { - LOG.error(String.format(ACK_WAS_ALREADY_SENT, consumerTag)); - } - } - - public void autoConfirm() { - executeAutoAck(() -> { - confirmMessage(); - LOG.debug(String.format("[consumer tag: %s] A message was automatically confirmed after message processing", consumerTag)); - }); - } - - public void autoReject() { - executeAutoAck(() -> { - rejectMessage(); - LOG.debug(String.format("[consumer tag: %s] A message was automatically rejected due to error during message processing", consumerTag)); - }); - } - - public void autoRetry() { - executeAutoAck(() -> { - retryMessage(); - LOG.debug(String.format("[consumer tag: %s] A message was automatically rejected (with a requeue attempt) due to error during message processing", consumerTag)); - }); - } - - private void executeAutoAck(AutoAckAction ackAction) { - if (autoAcknowledgement && !acknowledgementSent.get()) { - ackAction.perform(); - } - } - - @FunctionalInterface - private interface AckAction { - void perform() throws Exception; - } - - @FunctionalInterface - private interface AutoAckAction { - void perform(); - } - -} diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java index a614fa81..a5520f1e 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java @@ -1,6 +1,8 @@ package io.github.tcdl.msb.adapters.amqp; import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; import java.io.IOException; @@ -44,7 +46,7 @@ public AmqpMessageConsumer(Channel channel, ExecutorService consumerThreadPool, @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { long deliveryTag = envelope.getDeliveryTag(); - AmqpAcknowledgementHandler ackHandler = createAcknowledgementHandler( + AcknowledgementHandlerInternal ackHandler = createAcknowledgementHandler( getChannel(), consumerTag, deliveryTag, envelope.isRedeliver()); try { Charset charset = amqpBrokerConfig.getCharset(); @@ -68,9 +70,11 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp ackHandler.autoReject(); } } - - AmqpAcknowledgementHandler createAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) { - return new AmqpAcknowledgementHandler(channel, consumerTag, deliveryTag, isRequeueRejectedMessages); + + AcknowledgementHandlerInternal createAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) { + AmqpAcknowledgementAdapter adapter = new AmqpAcknowledgementAdapter(channel, consumerTag, deliveryTag); + String messageTextIdentifier = String.format("consumer tag: %s", consumerTag); + return new AcknowledgementHandlerImpl(adapter, isRequeueRejectedMessages, messageTextIdentifier); } } diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java index 2751ad34..2593ede5 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java @@ -1,7 +1,7 @@ package io.github.tcdl.msb.adapters.amqp; import io.github.tcdl.msb.adapters.ConsumerAdapter; - +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,10 +14,10 @@ public class AmqpMessageProcessingTask implements Runnable { final String consumerTag; final String body; final ConsumerAdapter.RawMessageHandler msgHandler; - final AmqpAcknowledgementHandler ackHandler; + final AcknowledgementHandlerInternal ackHandler; - public AmqpMessageProcessingTask(String consumerTag, String body, ConsumerAdapter.RawMessageHandler msgHandler, - AmqpAcknowledgementHandler ackHandler) { + public AmqpMessageProcessingTask(String consumerTag, String body, ConsumerAdapter.RawMessageHandler msgHandler, + AcknowledgementHandlerInternal ackHandler) { this.consumerTag = consumerTag; this.body = body; this.msgHandler = msgHandler; diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java index 11148d66..3657173e 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java @@ -6,21 +6,22 @@ import java.util.stream.IntStream; +import io.github.tcdl.msb.acknowledge.AcknowledgementAdapter; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import com.rabbitmq.client.Channel; @RunWith(MockitoJUnitRunner.class) public class AmqpAcknowledgementHandlerTest { - private AmqpAcknowledgementHandler handler; + private AcknowledgementHandlerImpl handler; @Mock - private Channel mockChannel; + private AcknowledgementAdapter acknowledgementAdapter; private long deliveryTag = 123123123; @@ -149,21 +150,21 @@ public void testAutoRetryRequeueMessageOnce() throws Exception { public void testAutoConfirmIgnoredWhenAutoAcknowledgementDisabled() throws Exception { handler.setAutoAcknowledgement(false); handler.autoConfirm(); - verifyNoMoreInteractions(mockChannel); + verifyNoMoreInteractions(acknowledgementAdapter); } @Test public void testAutoRejectIgnoredWhenAutoAcknowledgementDisabled() throws Exception { handler.setAutoAcknowledgement(false); handler.autoReject(); - verifyNoMoreInteractions(mockChannel); + verifyNoMoreInteractions(acknowledgementAdapter); } @Test public void testAutoRetryIgnoredWhenAutoAcknowledgementDisabled() throws Exception { handler.setAutoAcknowledgement(false); handler.autoRetry(); - verifyNoMoreInteractions(mockChannel); + verifyNoMoreInteractions(acknowledgementAdapter); } @Test @@ -208,18 +209,18 @@ public void testAutoRejectIgnoredWhenRejectedByClient() throws Exception { } private void verifySingleConfirm() throws Exception { - verify(mockChannel, times(1)).basicAck(deliveryTag, false); - verifyNoMoreInteractions(mockChannel); + verify(acknowledgementAdapter, times(1)).confirm(); + verifyNoMoreInteractions(acknowledgementAdapter); } private void verifySingleRetry() throws Exception { - verify(mockChannel, times(1)).basicReject(deliveryTag, true); - verifyNoMoreInteractions(mockChannel); + verify(acknowledgementAdapter, times(1)).retry(); + verifyNoMoreInteractions(acknowledgementAdapter); } private void verifySingleReject() throws Exception { - verify(mockChannel, times(1)).basicReject(deliveryTag, false); - verifyNoMoreInteractions(mockChannel); + verify(acknowledgementAdapter, times(1)).reject(); + verifyNoMoreInteractions(acknowledgementAdapter); } private void submitMultipleConfirmRejectRequests() { @@ -238,8 +239,8 @@ private void submitMultipleAutoConfirmAutoRejectRequests() { }); } - private AmqpAcknowledgementHandler getHandler(boolean isMessageRedelivered) { - return new AmqpAcknowledgementHandler(mockChannel, "any", deliveryTag, isMessageRedelivered); + private AcknowledgementHandlerImpl getHandler(boolean isMessageRedelivered) { + return new AcknowledgementHandlerImpl(acknowledgementAdapter, isMessageRedelivered, "id = 123"); } } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java index fcf0b560..334592d1 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java @@ -8,6 +8,7 @@ import static org.mockito.Mockito.*; import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl; import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; import java.io.IOException; @@ -41,7 +42,7 @@ public class AmqpMessageConsumerTest { private AmqpBrokerConfig mockBrokerConfig; @Mock - private AmqpAcknowledgementHandler amqpAcknowledgementHandler; + private AcknowledgementHandlerImpl amqpAcknowledgementHandler; private AmqpMessageConsumer amqpMessageConsumer; @@ -51,7 +52,7 @@ public void setUp() { amqpMessageConsumer = new AmqpMessageConsumer(mockChannel, mockExecutorService, mockMessageHandler, mockBrokerConfig) { @Override - AmqpAcknowledgementHandler createAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) { + AcknowledgementHandlerImpl createAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) { return amqpAcknowledgementHandler; } }; diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java index 7b0838e6..4782eeab 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java @@ -9,6 +9,7 @@ import java.io.IOException; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl; import org.junit.Before; import org.junit.Test; @@ -29,7 +30,7 @@ public class AmqpMessageProcessingTaskTest { private ConsumerAdapter.RawMessageHandler mockMessageHandler; @Mock - private AmqpAcknowledgementHandler mockAcknowledgementHandler; + private AcknowledgementHandlerImpl mockAcknowledgementHandler; private AmqpMessageProcessingTask task; diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java index 8d7bae60..c0bf451e 100644 --- a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java +++ b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java @@ -1,7 +1,7 @@ package io.github.tcdl.msb.cli; import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.api.AcknowledgementHandler; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; import io.github.tcdl.msb.api.exception.JsonConversionException; import java.io.IOException; @@ -32,7 +32,7 @@ public CliMessageHandler(CliMessageSubscriber subscriber, List follow, b * @throws JsonConversionException if some problems during parsing JSON */ @Override - public void onMessage(String jsonMessage, AcknowledgementHandler handler) { + public void onMessage(String jsonMessage, AcknowledgementHandlerInternal handler) { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index 7bb85a32..a2106ef6 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -1,9 +1,7 @@ package io.github.tcdl.msb; import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.api.AcknowledgementHandler; -import io.github.tcdl.msb.api.exception.JsonConversionException; -import io.github.tcdl.msb.api.exception.JsonSchemaValidationException; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.MetaMessage; import io.github.tcdl.msb.config.MsbConfig; @@ -85,29 +83,43 @@ public void end() { * * @param jsonMessage message to process */ - protected void handleRawMessage(String jsonMessage, AcknowledgementHandler acknowledgeHandler) { + protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerInternal acknowledgeHandler) { LOG.debug("Topic [{}] message received [{}]", this.topic, jsonMessage); channelMonitorAgent.consumerMessageReceived(topic); + Message message; + try { - if (msbConfig.getSchema() != null && !Utils.isServiceTopic(topic) && msbConfig.isValidateMessage()) { - LOG.debug("Validating schema for {}", jsonMessage); - validator.validate(jsonMessage, msbConfig.getSchema()); - } - LOG.debug("Parsing message {}", jsonMessage); - Message message = Utils.fromJson(jsonMessage, Message.class, messageMapper); - LOG.debug("Message has been successfully parsed {}", jsonMessage); - - if (!isMessageExpired(message)) { - messageHandler.handleMessage(message, acknowledgeHandler); - } else { - LOG.warn("Expired message: {}", jsonMessage); - acknowledgeHandler.rejectMessage(); - } - } catch (JsonConversionException | JsonSchemaValidationException e) { + message = parseMessage(jsonMessage); + } catch (Exception e) { LOG.error("Unable to process consumed message {}", jsonMessage, e); - acknowledgeHandler.rejectMessage(); + acknowledgeHandler.autoReject(); + return; + } + + if (isMessageExpired(message)) { + LOG.warn("Expired message: {}", jsonMessage); + acknowledgeHandler.autoReject(); + return; + } + + try { + messageHandler.handleMessage(message, acknowledgeHandler); + } catch (Exception e) { + LOG.warn("Error while trying to handle a message: {}", jsonMessage, e); + acknowledgeHandler.autoRetry(); + } + } + + private Message parseMessage(String jsonMessage) { + if (msbConfig.getSchema() != null && !Utils.isServiceTopic(topic) && msbConfig.isValidateMessage()) { + LOG.debug("Validating schema for {}", jsonMessage); + validator.validate(jsonMessage, msbConfig.getSchema()); } + LOG.debug("Parsing message {}", jsonMessage); + Message result = Utils.fromJson(jsonMessage, Message.class, messageMapper); + LOG.debug("Message has been successfully parsed {}", jsonMessage); + return result; } private boolean isMessageExpired(Message message) { diff --git a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementAdapter.java b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementAdapter.java new file mode 100644 index 00000000..551f192e --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementAdapter.java @@ -0,0 +1,25 @@ +package io.github.tcdl.msb.acknowledge; + +/** + * Adapter that provides low-level acknowledgement management methods for {@link AcknowledgementHandlerImpl}. + */ +public interface AcknowledgementAdapter { + + /** + * Confirm a message. + * @throws Exception + */ + void confirm() throws Exception; + + /** + * Reject a message. + * @throws Exception + */ + void reject() throws Exception; + + /** + * Requeue a message. + * @throws Exception + */ + void retry() throws Exception; +} diff --git a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java new file mode 100644 index 00000000..bad44110 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java @@ -0,0 +1,121 @@ +package io.github.tcdl.msb.acknowledge; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.github.tcdl.msb.api.AcknowledgementHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class provides acknowledgement mechanism implementation that handles + * both implicit (provided by {@link AcknowledgementHandlerInternal} and used by the library) + * and explicit (provided by {@link AcknowledgementHandler} and used by library clients) messages acknowledge means. + */ +public class AcknowledgementHandlerImpl implements AcknowledgementHandlerInternal { + + private static final String ACK_WAS_ALREADY_SENT = "[%s] Acknowledgement was already sent during message processing."; + + private static final Logger LOG = LoggerFactory.getLogger(AcknowledgementHandlerImpl.class); + + final AcknowledgementAdapter acknowledgementAdapter; + final boolean isMessageRedelivered; + final String messageTextIdentifier; + + final AtomicBoolean acknowledgementSent = new AtomicBoolean(false); + volatile boolean autoAcknowledgement = true; + + public AcknowledgementHandlerImpl(AcknowledgementAdapter acknowledgementAdapter, + boolean isMessageRedelivered, String messageTextIdentifier) { + super(); + this.acknowledgementAdapter = acknowledgementAdapter; + this.isMessageRedelivered = isMessageRedelivered; + this.messageTextIdentifier = messageTextIdentifier; + } + + public boolean isAutoAcknowledgement() { + return autoAcknowledgement; + } + + public void setAutoAcknowledgement(boolean autoAcknowledgement) { + this.autoAcknowledgement = autoAcknowledgement; + } + + @Override + public void confirmMessage() { + executeAck("confirm", () -> { + acknowledgementAdapter.confirm(); + LOG.debug(String.format("[%s] A message was confirmed", messageTextIdentifier)); + }); + } + + @Override + public void retryMessage() { + executeAck("requeue", () -> { + if(!isMessageRedelivered) { + acknowledgementAdapter.retry(); + LOG.debug(String.format("[%s] A message was rejected with requeue", messageTextIdentifier)); + } else { + acknowledgementAdapter.reject(); + LOG.warn(String.format("[%s] Can't requeue message because it already was redelivered once, discarding it instead", messageTextIdentifier)); + } + }); + } + + @Override + public void rejectMessage() { + executeAck("reject", () -> { + acknowledgementAdapter.reject(); + LOG.debug(String.format("[%s] A message was discarded", messageTextIdentifier)); + }); + } + + private void executeAck(String actionName, AckAction ackAction) { + if (acknowledgementSent.compareAndSet(false, true)) { + try { + ackAction.perform(); + } catch (Exception e) { + LOG.error(String.format("[%s] Got exception when trying to %s a message:", messageTextIdentifier, actionName), e); + } + } else { + LOG.error(String.format(ACK_WAS_ALREADY_SENT, messageTextIdentifier)); + } + } + + public void autoConfirm() { + executeAutoAck(() -> { + confirmMessage(); + LOG.debug(String.format("[%s] A message was automatically confirmed after message processing", messageTextIdentifier)); + }); + } + + public void autoReject() { + executeAutoAck(() -> { + rejectMessage(); + LOG.debug(String.format("[%s] A message was automatically rejected due to error during message processing", messageTextIdentifier)); + }); + } + + public void autoRetry() { + executeAutoAck(() -> { + retryMessage(); + LOG.debug(String.format("[%s] A message was automatically rejected (with a requeue attempt) due to error during message processing", messageTextIdentifier)); + }); + } + + private void executeAutoAck(AutoAckAction ackAction) { + if (autoAcknowledgement && !acknowledgementSent.get()) { + ackAction.perform(); + } + } + + @FunctionalInterface + private interface AckAction { + void perform() throws Exception; + } + + @FunctionalInterface + private interface AutoAckAction { + void perform(); + } + +} diff --git a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerInternal.java b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerInternal.java new file mode 100644 index 00000000..2a8c5a77 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerInternal.java @@ -0,0 +1,24 @@ +package io.github.tcdl.msb.acknowledge; + +import io.github.tcdl.msb.api.AcknowledgementHandler; + +/** + * Interface for message acknowledgement used internally for implicit messages acknowledge. + */ +public interface AcknowledgementHandlerInternal extends AcknowledgementHandler { + + /** + * Implicit message acknowledge request invoked after client callback execution. + */ + void autoConfirm(); + + /** + * Implicit message reject request invoked when a message is expired or corrupted. + */ + void autoReject(); + + /** + * Implicit message requeue request invoked when there was an exception during a client callback execution. + */ + void autoRetry(); +} diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java index 440747fb..11d57460 100644 --- a/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java +++ b/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java @@ -1,6 +1,6 @@ package io.github.tcdl.msb.adapters; -import io.github.tcdl.msb.api.AcknowledgementHandler; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; import io.github.tcdl.msb.api.exception.ChannelException; /** @@ -33,7 +33,7 @@ interface RawMessageHandler { * @param jsonMessage incomming JSON message * @param acknowledgementHandler confirm/reject message handler */ - void onMessage(String jsonMessage, AcknowledgementHandler acknowledgementHandler); + void onMessage(String jsonMessage, AcknowledgementHandlerInternal acknowledgementHandler); } } diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index 0b730a7c..551da233 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -6,7 +6,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.api.AcknowledgementHandler; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.MetaMessage; @@ -47,7 +47,7 @@ public class ConsumerTest { private MessageHandler messageHandlerMock; @Mock - private AcknowledgementHandler acknowledgementHandler; + private AcknowledgementHandlerInternal acknowledgementHandlerMock; private Clock clock = Clock.systemDefaultZone(); @@ -116,7 +116,7 @@ public void testValidMessageProcessedBySubscriber() throws JsonConversionExcepti public void testExceptionWhileMessageConvertingProcessedBySubscriber() throws JsonConversionException { Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandler); + consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock); verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); } @@ -154,7 +154,7 @@ public void testHandleRawMessageConsumeFromTopicValidateThrowException() { MsbConfig msbConf = TestUtils.createMsbConfigurations(); Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandler); + consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock); verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); // no processing } @@ -164,7 +164,7 @@ public void testHandleRawMessageConsumeFromServiceTopicValidateThrowException() MsbConfig msbConf = TestUtils.createMsbConfigurations(); Consumer consumer = new Consumer(adapterMock, service_topic, messageHandlerMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandler); + consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock); verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); // no processing } @@ -173,7 +173,7 @@ public void testHandleRawMessageConsumeFromTopicExpiredMessage() throws JsonConv Message expiredMessage = createExpiredMsbRequestMessageWithTopicTo(TOPIC); Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); - consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper), acknowledgementHandler); + consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper), acknowledgementHandlerMock); verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); } From 0b77715994fbc585e2aab19b5aa46ecc45df397f Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 8 Dec 2015 17:32:00 +0200 Subject: [PATCH 023/226] WEB-16081 ack - unit testing; minor logging improvements --- .../amqp/AmqpAcknowledgementAdapterTest.java | 45 +++++++++++++++++++ .../github/tcdl/msb/collector/Collector.java | 21 +++++---- .../tcdl/msb/collector/TimeoutManager.java | 18 ++++---- .../java/io/github/tcdl/msb/ConsumerTest.java | 22 +++++++-- .../msb/collector/TimeoutManagerTest.java | 10 ++++- 5 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementAdapterTest.java diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementAdapterTest.java new file mode 100644 index 00000000..68ef6278 --- /dev/null +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementAdapterTest.java @@ -0,0 +1,45 @@ +package io.github.tcdl.msb.adapters.amqp; + +import com.rabbitmq.client.Channel; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class AmqpAcknowledgementAdapterTest { + private final static String MESSAGE_TEXT_ID = "id = 123"; + private final static long DELIVERY_TAG = 12337564; + + private AmqpAcknowledgementAdapter adapter; + + @Mock + private Channel channel; + + @Before + public void setUp() { + adapter = new AmqpAcknowledgementAdapter(channel, MESSAGE_TEXT_ID, DELIVERY_TAG); + } + + @Test + public void testConfirmSuccess() throws Exception { + adapter.confirm(); + verify(channel, times(1)).basicAck(DELIVERY_TAG, false); + } + + @Test + public void testRejectSuccess() throws Exception { + adapter.reject(); + verify(channel, times(1)).basicReject(DELIVERY_TAG, false); + } + + @Test + public void testRetrySuccess() throws Exception { + adapter.retry(); + verify(channel, times(1)).basicReject(DELIVERY_TAG, true); + } + +} diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index 320d0020..843ff952 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -125,12 +125,14 @@ public void listenForResponses() { } public void handleMessage(Message incomingMessage, AcknowledgementHandler acknowledgeHandler) { - LOG.debug("Received {}", incomingMessage); + LOG.debug("[correlation ids: {}-{}] Received {}", + requestMessage.getCorrelationId(), incomingMessage.getCorrelationId(), incomingMessage); JsonNode rawPayload = incomingMessage.getRawPayload(); MessageContext messageContext = createMessageContext(acknowledgeHandler, incomingMessage); if (Utils.isPayloadPresent(rawPayload)) { - LOG.debug("Received Payload {}", rawPayload); + LOG.debug("[correlation ids: {}-{}] Received Payload {}", + requestMessage.getCorrelationId(), incomingMessage.getCorrelationId(), rawPayload); payloadMessages.add(incomingMessage); onRawResponse.ifPresent(handler -> handler.accept(incomingMessage, messageContext)); @@ -139,7 +141,8 @@ public void handleMessage(Message incomingMessage, AcknowledgementHandler acknow incResponsesRemaining(-1); } else { - LOG.debug("Received {}", incomingMessage.getAck()); + LOG.debug("[correlation ids: {}-{}] Received {}", + requestMessage.getCorrelationId(), incomingMessage.getCorrelationId(), incomingMessage.getAck()); ackMessages.add(incomingMessage); onAcknowledge.ifPresent(handler -> handler.accept(incomingMessage.getAck(), messageContext)); } @@ -155,6 +158,7 @@ public void handleMessage(Message incomingMessage, AcknowledgementHandler acknow return; } + LOG.debug("[correlation ids: {}] All messages has been received", requestMessage.getCorrelationId()); end(); } @@ -163,7 +167,7 @@ MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandle } protected void end() { - LOG.debug("Stop response processing"); + LOG.debug("[correlation id: {}] Stop response processing ", requestMessage.getCorrelationId()); cancelAckTimeoutTask(); cancelResponseTimeoutTask(); @@ -177,7 +181,8 @@ void processAck(Acknowledge acknowledge) { return; if (acknowledge.getResponsesRemaining() != null) { - LOG.debug("Responses remaining for responderId [{}] is set to {}", acknowledge.getResponderId(), + LOG.debug("[correlation id: {}] Responses remaining for responderId [{}] is set to {}", + requestMessage.getCorrelationId(), acknowledge.getResponderId(), setResponsesRemainingForResponderId(acknowledge.getResponderId(), acknowledge.getResponsesRemaining())); } @@ -255,17 +260,17 @@ private Integer setResponsesRemainingForResponderId(String responderId, int resp public void waitForResponses() { int newTimeoutMs = this.currentTimeoutMs - toIntExact(clock.instant().toEpochMilli() - this.startedAt); - LOG.debug("Waiting for responses until {}.", clock.instant().plus(newTimeoutMs, ChronoUnit.MILLIS)); + LOG.debug("[correlation id: {}] Waiting for responses until {}.", requestMessage.getCorrelationId(), clock.instant().plus(newTimeoutMs, ChronoUnit.MILLIS)); this.responseTimeoutFuture = timeoutManager.enableResponseTimeout(newTimeoutMs, this); } void waitForAcks() { if (ackTimeoutFuture == null) { - LOG.debug("Waiting for ack until {}.", this.waitForAcksUntil); + LOG.debug("[correlation id: {}] Waiting for ack until {}.", requestMessage.getCorrelationId(), this.waitForAcksUntil); long ackTimeoutMs = waitForAcksUntil.toEpochMilli() - clock.instant().toEpochMilli(); ackTimeoutFuture = timeoutManager.enableAckTimeout(toIntExact(ackTimeoutMs), this); } else { - LOG.debug("Ack timeout is already scheduled"); + LOG.debug("[correlation id: {}] Ack timeout is already scheduled", requestMessage.getCorrelationId()); } } diff --git a/core/src/main/java/io/github/tcdl/msb/collector/TimeoutManager.java b/core/src/main/java/io/github/tcdl/msb/collector/TimeoutManager.java index a7a3c9e8..4d1921ed 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/TimeoutManager.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/TimeoutManager.java @@ -23,44 +23,44 @@ public TimeoutManager(int threadPoolSize) { } protected ScheduledFuture enableResponseTimeout(int timeoutMs, Collector collector) { - LOG.debug("Enabling response timeout for {} ms", timeoutMs); + LOG.debug("[correlation id: {}] Enabling response timeout for {} ms", collector.getRequestMessage().getCorrelationId(), timeoutMs); if (timeoutMs <= 0) { - LOG.debug("Unable to schedule timeout with negative delay : {}", timeoutMs); + LOG.debug("[correlation id: {}] Unable to schedule timeout with negative delay : {}", collector.getRequestMessage().getCorrelationId(), timeoutMs); return null; } try { return timeoutExecutorDecorator.schedule(() -> { - LOG.debug("Response timeout expired."); + LOG.debug("[correlation id: {}] Response timeout expired.", collector.getRequestMessage().getCorrelationId()); collector.end(); }, timeoutMs, TimeUnit.MILLISECONDS); } catch (RejectedExecutionException e) { - LOG.warn("Unable to schedule task for execution", e); + LOG.warn("[correlation id: {}] Unable to schedule task for execution", collector.getRequestMessage().getCorrelationId(), e); return null; } } protected ScheduledFuture enableAckTimeout(int timeoutMs, Collector collector) { - LOG.debug("Enabling ack timeout for {} ms", timeoutMs); + LOG.debug("[correlation id: {}] Enabling ack timeout for {} ms", collector.getRequestMessage().getCorrelationId(), timeoutMs); if (timeoutMs <= 0) { - LOG.debug("Unable to schedule timeout with negative delay : {}", timeoutMs); + LOG.debug("[correlation id: {}] Unable to schedule timeout with negative delay : {}", collector.getRequestMessage().getCorrelationId(), timeoutMs); return null; } try { return timeoutExecutorDecorator.schedule(() -> { if (collector.isAwaitingResponses()) { - LOG.debug("Ack timeout expired, but waiting for responses."); + LOG.debug("[correlation id: {}] Ack timeout expired, but waiting for responses.", collector.getRequestMessage().getCorrelationId()); return; } - LOG.debug("Ack timeout expired."); + LOG.debug("[correlation id: {}] Ack timeout expired.", collector.getRequestMessage().getCorrelationId()); collector.end(); }, timeoutMs, TimeUnit.MILLISECONDS); } catch (RejectedExecutionException e) { - LOG.warn("Unable to schedule task for execution", e); + LOG.warn("[correlation id: {}] Unable to schedule task for execution", collector.getRequestMessage().getCorrelationId(), e); return null; } } diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index 551da233..7d0f0719 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -1,10 +1,8 @@ package io.github.tcdl.msb; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; + import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; import io.github.tcdl.msb.api.exception.JsonConversionException; @@ -156,6 +154,7 @@ public void testHandleRawMessageConsumeFromTopicValidateThrowException() { consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock); verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); // no processing + verify(acknowledgementHandlerMock, times(1)).autoReject(); } @Test @@ -175,6 +174,21 @@ public void testHandleRawMessageConsumeFromTopicExpiredMessage() throws JsonConv consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper), acknowledgementHandlerMock); verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); + verify(acknowledgementHandlerMock, times(1)).autoReject(); + } + + @Test + public void testHandleMessageException() throws JsonConversionException { + Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); + messageHandlerMock.handleMessage(any(Message.class), any()); + + doThrow(new RuntimeException()).when(messageHandlerMock).handleMessage(any(), any()); + + Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + + consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); + + verify(acknowledgementHandlerMock, times(1)).autoRetry(); } private Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) { diff --git a/core/src/test/java/io/github/tcdl/msb/collector/TimeoutManagerTest.java b/core/src/test/java/io/github/tcdl/msb/collector/TimeoutManagerTest.java index ccbba0dc..9956c007 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/TimeoutManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/TimeoutManagerTest.java @@ -4,7 +4,8 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; - +import io.github.tcdl.msb.support.TestUtils; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -14,7 +15,12 @@ public class TimeoutManagerTest { @Mock - Collector mockCollector; + private Collector mockCollector; + + @Before + public void setUp() { + when(mockCollector.getRequestMessage()).thenReturn(TestUtils.createSimpleRequestMessage("123")); + } @Test public void testEnableResponseTimeout() { From 0fff35bef9376ccf61e8d2d96300629f250665b4 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Thu, 10 Dec 2015 17:38:58 +0200 Subject: [PATCH 024/226] Removed requeueRejectedMessages amqp.config option --- .../src/main/resources/application.conf | 1 - .../msb/config/amqp/AmqpBrokerConfig.java | 16 +++------ amqp/src/main/resources/amqp.conf | 1 - .../amqp/AmqpAdapterFactoryExecutorTest.java | 25 +++++++------ .../adapters/amqp/AmqpAdapterFactoryTest.java | 3 +- .../amqp/AmqpConsumerAdapterTest.java | 2 +- .../msb/config/amqp/AmqpBrokerConfigTest.java | 35 ------------------- cli/src/main/resources/application.conf | 1 - examples/src/main/resources/application.conf | 2 +- 9 files changed, 19 insertions(+), 67 deletions(-) diff --git a/acceptance/src/main/resources/application.conf b/acceptance/src/main/resources/application.conf index 52825bd1..e07758fe 100644 --- a/acceptance/src/main/resources/application.conf +++ b/acceptance/src/main/resources/application.conf @@ -24,7 +24,6 @@ msbConfig { consumerThreadPoolSize = 5 # -1 means unlimited consumerThreadPoolQueueCapacity = 20 - requeueRejectedMessages = true } } diff --git a/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java b/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java index 905db5e3..fa9720be 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java +++ b/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java @@ -23,14 +23,13 @@ public class AmqpBrokerConfig { private final boolean durable; private final int consumerThreadPoolSize; private final int consumerThreadPoolQueueCapacity; - private final boolean requeueRejectedMessages; private final int heartbeatIntervalSec; private final long networkRecoveryIntervalMs; private final int prefetchCount; public AmqpBrokerConfig(Charset charset, String host, int port, Optional username, Optional password, Optional virtualHost, boolean useSSL, - Optional groupId, boolean durable, int consumerThreadPoolSize, int consumerThreadPoolQueueCapacity, boolean requeueRejectedMessages, + Optional groupId, boolean durable, int consumerThreadPoolSize, int consumerThreadPoolQueueCapacity, int heartbeatIntervalSec, long networkRecoveryIntervalMs, int prefetchCount) { this.charset = charset; this.port = port; @@ -43,7 +42,6 @@ public AmqpBrokerConfig(Charset charset, String host, int port, this.durable = durable; this.consumerThreadPoolSize = consumerThreadPoolSize; this.consumerThreadPoolQueueCapacity = consumerThreadPoolQueueCapacity; - this.requeueRejectedMessages = requeueRejectedMessages; this.heartbeatIntervalSec = heartbeatIntervalSec; this.networkRecoveryIntervalMs = networkRecoveryIntervalMs; this.prefetchCount = prefetchCount; @@ -61,7 +59,6 @@ public static class AmqpBrokerConfigBuilder { private boolean durable; private int consumerThreadPoolSize; private int consumerThreadPoolQueueCapacity; - private boolean requeueRejectedMessages; private int heartbeatIntervalSec; private long networkRecoveryIntervalMs; private int prefetchCount; @@ -91,7 +88,6 @@ public AmqpBrokerConfigBuilder withConfig(Config config) { this.durable = ConfigurationUtil.getBoolean(config, "durable"); this.consumerThreadPoolSize = ConfigurationUtil.getInt(config, "consumerThreadPoolSize"); this.consumerThreadPoolQueueCapacity = ConfigurationUtil.getInt(config, "consumerThreadPoolQueueCapacity"); - this.requeueRejectedMessages = ConfigurationUtil.getBoolean(config, "requeueRejectedMessages"); this.heartbeatIntervalSec = ConfigurationUtil.getInt(config, "heartbeatIntervalSec"); this.networkRecoveryIntervalMs = ConfigurationUtil.getLong(config, "networkRecoveryIntervalMs"); this.prefetchCount = ConfigurationUtil.getInt(config, "prefetchCount"); @@ -104,7 +100,7 @@ public AmqpBrokerConfigBuilder withConfig(Config config) { public AmqpBrokerConfig build() { return new AmqpBrokerConfig(charset, host, port, username, password, virtualHost, useSSL, groupId, durable, consumerThreadPoolSize, consumerThreadPoolQueueCapacity, - requeueRejectedMessages, heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); + heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); } } @@ -156,10 +152,6 @@ public int getConsumerThreadPoolQueueCapacity() { return consumerThreadPoolQueueCapacity; } - public boolean isRequeueRejectedMessages() { - return requeueRejectedMessages; - } - public int getHeartbeatIntervalSec() { return heartbeatIntervalSec; } @@ -175,10 +167,10 @@ public int getPrefetchCount() { @Override public String toString() { return String.format("AmqpBrokerConfig [charset=%s, host=%s, port=%d, username=%s, password=xxx, virtualHost=%s, useSSL=%s, groupId=%s, durable=%s, " - + "consumerThreadPoolSize=%s, consumerThreadPoolQueueCapacity=%s, requeueRejectedMessages=%s, heartbeatIntervalSec=%s, " + + "consumerThreadPoolSize=%s, consumerThreadPoolQueueCapacity=%s, heartbeatIntervalSec=%s, " + "networkRecoveryIntervalMs=%s, prefetchCount=%s]", charset, host, port, username, virtualHost, useSSL, groupId, durable, consumerThreadPoolSize, consumerThreadPoolQueueCapacity, - requeueRejectedMessages, heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); + heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); } } \ No newline at end of file diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf index d4fc254a..44825893 100644 --- a/amqp/src/main/resources/amqp.conf +++ b/amqp/src/main/resources/amqp.conf @@ -18,7 +18,6 @@ config.amqp = { consumerThreadPoolSize = 5 # -1 means unlimited consumerThreadPoolQueueCapacity = 20 - requeueRejectedMessages = true # Interval of the heartbeats that are used to detect broken connections. Zero for none. See for more details: https://www.rabbitmq.com/heartbeats.html heartbeatIntervalSec = 1 diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java index 0b440400..35323ec6 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java @@ -1,12 +1,11 @@ package io.github.tcdl.msb.adapters.amqp; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Recoverable; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; import io.github.tcdl.msb.config.MsbConfig; -import org.junit.Test; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; @@ -14,11 +13,13 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.withSettings; +import org.junit.Test; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.Recoverable; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; public class AmqpAdapterFactoryExecutorTest { @@ -48,7 +49,6 @@ public void testCreateConsumerThreadPoolBoundedQueue() { + " charsetName = \"UTF-8\"\n" + " consumerThreadPoolSize = 5\n" + " consumerThreadPoolQueueCapacity = 20\n" - + " requeueRejectedMessages = true\n" + " }"; Config msbConfig = ConfigFactory.parseString(String.format(basicConfig, brokerConf)); @@ -75,7 +75,6 @@ public void testCreateConsumerThreadPoolUnboundedQueue() { + " charsetName = \"UTF-8\"\n" + " consumerThreadPoolSize = 5\n" + " consumerThreadPoolQueueCapacity = -1\n" - + " requeueRejectedMessages = true\n" + " }"; Config msbConfig = ConfigFactory.parseString(String.format(basicConfig, brokerConf)); MsbConfig msbConfigurations = new MsbConfig(msbConfig); diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java index 3d79808e..eb4d5cab 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java @@ -41,7 +41,6 @@ public class AmqpAdapterFactoryTest { final boolean durable = false; final int consumerThreadPoolSize = 5; final int consumerThreadPoolQueueCapacity = 20; - final boolean requeueRejectedMessages = true; final int heartbeatIntervalSec = 1; final long networkRecoveryIntervalMs = 5000; final int prefetchCount = 1; @@ -83,7 +82,7 @@ public void setUp() { amqpConfig = new AmqpBrokerConfig(charset, host, port, Optional.of(username), Optional.of(password), Optional.of(virtualHost), useSSL, Optional.of(groupId), durable, - consumerThreadPoolSize, consumerThreadPoolQueueCapacity, requeueRejectedMessages, + consumerThreadPoolSize, consumerThreadPoolQueueCapacity, heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); amqpAdapterFactory = new AmqpAdapterFactory() { diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java index 818e965a..8aed9ba5 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java @@ -126,7 +126,7 @@ public void testUnsubscribe() throws IOException { private AmqpConsumerAdapter createAdapter(String topic, String groupId, boolean durable) { AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(), - false, Optional.of(groupId), durable, 5, 20, true, 1, 5000, 1); + false, Optional.of(groupId), durable, 5, 20, 1, 5000, 1); return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, mockConsumerThreadPool); } } \ No newline at end of file diff --git a/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java b/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java index 5807f6a3..dccb3865 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java @@ -26,7 +26,6 @@ public class AmqpBrokerConfigTest { final boolean durable = false; final int consumerThreadPoolSize = 5; final int consumerThreadPoolQueueCapacity = 20; - final boolean requeueRejectedMessages = true; final int heartbeatIntervalSec = 1; final long networkRecoveryIntervalMs = 5000; final int prefetchCount = 1; @@ -45,7 +44,6 @@ public void testBuildAmqpBrokerConfig() { + " durable = " + durable + "\n" + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -62,7 +60,6 @@ public void testBuildAmqpBrokerConfig() { assertEquals(brokerConfig.isDurable(), durable); assertEquals(brokerConfig.getConsumerThreadPoolSize(), consumerThreadPoolSize); assertEquals(brokerConfig.getConsumerThreadPoolQueueCapacity(), consumerThreadPoolQueueCapacity); - assertEquals(brokerConfig.isRequeueRejectedMessages(), requeueRejectedMessages); assertEquals(brokerConfig.getUsername().get(), username); assertEquals(brokerConfig.getPassword().get(), password); @@ -86,7 +83,6 @@ public void testOptionalConfigurationOptions() { + " durable = " + durable + "\n" + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -117,7 +113,6 @@ public void testHostConfigurationOption() { + " durable = " + durable + "\n" + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -139,7 +134,6 @@ public void testPortConfigurationOption() { + " durable = " + durable + "\n" + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -161,7 +155,6 @@ public void testDurableConfigurationOption() { + " groupId = \"" + groupId + "\"\n" + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -183,7 +176,6 @@ public void testConsumerThreadPoolSizeConfigurationOption() { + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -205,7 +197,6 @@ public void testConsumerThreadPoolQueueCapacityConfigurationOption() { + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -227,7 +218,6 @@ public void testCharsetConfigurationOption() { + " durable = " + durable + "\n" + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -282,28 +272,6 @@ public void testUseSSLConfigurationOption() { testMandatoryConfigurationOption(configStr, "useSSL"); } - @Test - public void testRequeueRejectedMessagesOption() { - String configStr = "config.amqp {" - + " charsetName = \"" + charsetName + "\"\n" - + " host = \"" + host + "\"\n" - + " port = \"" + port + "\"\n" - + " username = \"" + username + "\"\n" - + " password = \"" + password + "\"\n" - + " virtualHost = \"" + virtualHost + "\"\n" - + " useSSL = \"" + useSSL + "\"\n" - + " groupId = \"" + groupId + "\"\n" - + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" - + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" - + " prefetchCount = " + prefetchCount + "\n" - + "}"; - - testMandatoryConfigurationOption(configStr, "requeueRejectedMessages"); - } - @Test public void testHeartbeatIntervalOption() { String configStr = "config.amqp {" @@ -318,7 +286,6 @@ public void testHeartbeatIntervalOption() { + " durable = " + durable + "\n" + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" + "}"; @@ -340,7 +307,6 @@ public void testNetworkRecoveryIntervalOption() { + " durable = " + durable + "\n" + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " prefetchCount = " + prefetchCount + "\n" + "}"; @@ -362,7 +328,6 @@ public void testPrefetchCountOption() { + " durable = " + durable + "\n" + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " requeueRejectedMessages = " + requeueRejectedMessages + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + "}"; diff --git a/cli/src/main/resources/application.conf b/cli/src/main/resources/application.conf index dcc41c71..01784b50 100644 --- a/cli/src/main/resources/application.conf +++ b/cli/src/main/resources/application.conf @@ -21,7 +21,6 @@ msbConfig { consumerThreadPoolSize = 5 # -1 means unlimited consumerThreadPoolQueueCapacity = 20 - requeueRejectedMessages = true prefetchCount = 0 } diff --git a/examples/src/main/resources/application.conf b/examples/src/main/resources/application.conf index 52825bd1..51267c08 100644 --- a/examples/src/main/resources/application.conf +++ b/examples/src/main/resources/application.conf @@ -24,7 +24,7 @@ msbConfig { consumerThreadPoolSize = 5 # -1 means unlimited consumerThreadPoolQueueCapacity = 20 - requeueRejectedMessages = true + prefetchCount = 1 } } From 1449276d7bd5238051a9246ffb0ea9c2d3b7aa2c Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Fri, 11 Dec 2015 17:37:40 +0200 Subject: [PATCH 025/226] Described 1.4.x changes --- doc/MSB.md | 7 +++++-- release-notes.html | 14 +++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/doc/MSB.md b/doc/MSB.md index 6626b09c..72649de7 100644 --- a/doc/MSB.md +++ b/doc/MSB.md @@ -317,7 +317,7 @@ Here, the override field `name = ${?MSB_SERVICE_NAME}` simply vanishes if there' The _key values pairs_ described in this section are specific for the chosen Broker. The section `brokerConfig` from [reference.conf](/core/src/main/resources/reference.conf) file override values from [amqp.conf](/amqp/src/main/resources/amqp.conf). - `charsetName` – specifies charset for encoding and decoding of messages. Defaults to "UTF-8" as +`charsetName` – specifies charset for encoding and decoding of messages. Defaults to "UTF-8" as this encoding is used in MSB (Node.js). `host` – IP address of message broker, defaults to "127.0.0.1" @@ -353,7 +353,8 @@ More references on how to configure the broker to allow the remote access with t `heartbeatIntervalSec` - interval of the heartbeats that are used to detect broken connections. Zero for none. See for more details: https://www.rabbitmq.com/heartbeats.html. Defaults to 1 second. -`networkRecoveryIntervalMs` - interval of connection recovery attempts. See for more details: https://www.rabbitmq.com/api-guide.html#connection-recovery. Defaults to 5 seconds. +`prefetchCount` - Specify the limit number of unacknowledged messages on a channel when consuming. The default value is 10. + ## AMQP adapter @@ -367,6 +368,8 @@ An interest twist is related to consumption of incoming messages. The adapter ge The adapter supports AMQP connection recovery out of the box and it's always enabled. It's regulated by `heartbeatIntervalSec` and `networkRecoveryIntervalMs` configuration values (see [this section](#description-of-amqp-connection-configuration-fields) for more details). +The AMQP adapter supports explicit and automation message confirm/reject/retry acknowledgment. If a message was successfully processed, a microservice should enable confirms. In exceptional cases when the microservice is unable to handle messages successfully, reject or retry acknowledgment need be to send. If microservice doesn't explicitly send acknowledgment, MSB-Java can do it automatically after completion of message processing in current thread. If microservice provides more complexity message processing, for example in additional threads, AutoAcknowledgement need to be set to false. In this case a microservice is responsible for acknowledgment. + ## Channel monitoring Built-in channel monitoring allows to monitor micorservices/channels on the bus level. It consists of 2 components: diff --git a/release-notes.html b/release-notes.html index b30199ad..abe304f1 100644 --- a/release-notes.html +++ b/release-notes.html @@ -1,19 +1,23 @@ - Welcome to MSB-Java version 1.3.0 -

Welcome to MSB-Java version 1.3.0

+

Welcome to MSB-Java version 1.4.0

-

December 4, 2015

+

December 7, 2015

-
MSB-Java version 1.3.0 is a maintenance release that fixes some minor bags.
-It is also adds two new features.
+
MSB-Java version 1.4.0 is a maintenance release that fixes minor bags.
+It is also adds some new features.
 
 ------------------------------------------------------------------------------------
 
+Features of MSB-Java version 1.4.0:
+   - Added prefetch count for AMQP adapter 
+     (for details see https://www.rabbitmq.com/consumer-prefetch.html)
+   - Implemented explicit confirm/reject delivered messages  
+
 Features of MSB-Java version 1.3.0:
    - Removed REST-style payload constraint on parameters type 
    - Updated Requester behavior. It waits for acks even if configured to 

From 483270aef2075d55cdc652b2f4feda467fa1bb3b Mon Sep 17 00:00:00 2001
From: rkatsyuryna 
Date: Fri, 11 Dec 2015 17:45:01 +0200
Subject: [PATCH 026/226] WEB-13808:Broker hostname and port are not picked
 from Java system variables

---
 acceptance/pom.xml                            |  9 ++++----
 .../src/main/resources/application.conf       | 23 +------------------
 .../msb/adapters/amqp/AmqpAdapterFactory.java |  1 +
 .../io/github/tcdl/msb/config/MsbConfig.java  |  6 ++---
 examples/src/main/resources/application.conf  | 21 -----------------
 5 files changed, 10 insertions(+), 50 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 41b7b1f6..8f26659c 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -31,15 +31,16 @@
             io.github.tcdl.msb
             msb-java-amqp
         
-        
-            io.github.tcdl.msb
-            msb-java-examples
-        
         
             org.jbehave
             jbehave-core
             test
         
+        
+            io.github.tcdl.msb
+            msb-java-examples
+            test
+        
     
 
     
diff --git a/acceptance/src/main/resources/application.conf b/acceptance/src/main/resources/application.conf
index e07758fe..182abbe5 100644
--- a/acceptance/src/main/resources/application.conf
+++ b/acceptance/src/main/resources/application.conf
@@ -6,25 +6,4 @@ msbConfig {
      version = "1.0.1"
      instanceId = "msbd06a-ed59-4a39-9f95-811c5fb6ab87"
    }
-
-  brokerAdapterFactory = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory"
-
-  # Thread pool used for scheduling ack and response timeout tasks
-  timerThreadPoolSize: 2
-
-  # Enable/disable message validation against json schema
-  validateMessage = true
-
-  # Broker Adapter Defaults
-  brokerConfig = {
-    host = "127.0.0.1"
-    port = "5672"
-    groupId = "msb-java"
-    durable = false
-    consumerThreadPoolSize = 5
-    # -1 means unlimited
-    consumerThreadPoolQueueCapacity = 20
-  }
-
-}
-
+}
\ No newline at end of file
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
index 99959b88..75578a17 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
@@ -45,6 +45,7 @@ public class AmqpAdapterFactory implements AdapterFactory {
      */
     public void init(MsbConfig msbConfig) {
         amqpBrokerConfig = createAmqpBrokerConfig(msbConfig);
+        LOG.debug("MSB AMQP Broker configuration {}", amqpBrokerConfig);
         ConnectionFactory connectionFactory = createConnectionFactory(amqpBrokerConfig);
         Connection connection = createConnection(connectionFactory);
         connectionManager = createConnectionManager(connection);
diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java
index 9df9d469..678641d6 100644
--- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java
+++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java
@@ -44,14 +44,14 @@ public MsbConfig(Config loadedConfig) {
         this.timerThreadPoolSize = getInt(config, "timerThreadPoolSize");
         this.validateMessage = getBoolean(config, "validateMessage");
 
-        LOG.debug("MSB configuration {}", this);
+        LOG.debug("Loaded {}", this);
     }
 
     private String readJsonSchema() {
         try {
             return IOUtils.toString(getClass().getResourceAsStream("/schema.js"));
         } catch (IOException e) {
-            LOG.error("MSB configuration failed to load Json validation schema", this);
+            LOG.error("Failed to load Json validation schema", this);
             return null;
         }
     }
@@ -86,7 +86,7 @@ public int getTimerThreadPoolSize() {
 
     @Override
     public String toString() {
-        return String.format("MsbConfig [serviceDetails=%s, schema=%s, validateMessage=%b, timerThreadPoolSize=%d, brokerAdapterFactory=%s, brokerConfig=%s]", serviceDetails, schema, validateMessage, timerThreadPoolSize, brokerAdapterFactoryClass, brokerConfig);
+        return String.format("MsbConfig [serviceDetails=%s, schema=%s, validateMessage=%b, timerThreadPoolSize=%d, brokerAdapterFactory=%s, brokerConfig=%s]", serviceDetails, schema, validateMessage, timerThreadPoolSize, brokerAdapterFactoryClass, brokerConfig.root().render());
     }
 
 }
diff --git a/examples/src/main/resources/application.conf b/examples/src/main/resources/application.conf
index 51267c08..c9f73d66 100644
--- a/examples/src/main/resources/application.conf
+++ b/examples/src/main/resources/application.conf
@@ -6,26 +6,5 @@ msbConfig {
      version = "1.0.1"
      instanceId = "msbd06a-ed59-4a39-9f95-811c5fb6ab87"
    }
-
-  brokerAdapterFactory = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory"
-
-  # Thread pool used for scheduling ack and response timeout tasks
-  timerThreadPoolSize: 2
-
-  # Enable/disable message validation against json schema
-  validateMessage = true
-
-  # Broker Adapter Defaults
-  brokerConfig = {
-    host = "127.0.0.1"
-    port = "5672"
-    groupId = "msb-java"
-    durable = false
-    consumerThreadPoolSize = 5
-    # -1 means unlimited
-    consumerThreadPoolQueueCapacity = 20
-    prefetchCount = 1
-  }
-
 }
 

From 656ef205e6a8b146208ce71b1812c913ba0fc72a Mon Sep 17 00:00:00 2001
From: anha1 
Date: Tue, 15 Dec 2015 17:37:09 +0200
Subject: [PATCH 027/226] WEB-16678 time-consuming response callbacks no longer
 block incoming responses from being processed

---
 .../bdd/steps/ConfigurationSteps.java         |   7 +
 .../bdd/steps/RequesterResponderSteps.java    |  97 +++++++++++---
 .../scenarios/requester_responder.story       |  15 ++-
 amqp/pom.xml                                  |   5 +
 .../msb/adapters/amqp/AmqpAdapterFactory.java |   8 +-
 .../adapters/amqp/AmqpConsumerAdapter.java    |   9 +-
 .../adapters/amqp/AmqpMessageConsumer.java    |  10 +-
 .../amqp/AmqpMessageHandlerInvokeAdapter.java |  35 +++++
 .../amqp/AmqpMessageProcessingTask.java       |  22 ++--
 .../adapters/amqp/AmqpAdapterFactoryTest.java |  36 ++++--
 .../amqp/AmqpConsumerAdapterTest.java         |   7 +-
 .../amqp/AmqpMessageConsumerTest.java         |  25 +---
 .../AmqpMessageHandlerInvokeAdapterTest.java  |  47 +++++++
 .../amqp/AmqpMessageProcessingTaskTest.java   |  14 +-
 .../io/github/tcdl/msb/ChannelManager.java    |  20 +--
 .../java/io/github/tcdl/msb/Consumer.java     |  23 +++-
 .../tcdl/msb/MessageHandlerResolver.java      |  18 +++
 .../tcdl/msb/adapters/AdapterFactory.java     |   7 +
 .../adapters/MessageHandlerInvokeAdapter.java |  18 +++
 .../msb/adapters/mock/MockAdapterFactory.java |   7 +
 .../github/tcdl/msb/collector/Collector.java  |   4 +-
 .../tcdl/msb/collector/CollectorManager.java  |  19 +--
 ...SimpleMessageHandlerInvokeAdapterImpl.java |  17 +++
 .../SimpleMessageHandlerResolverImpl.java     |  24 ++++
 .../java/io/github/tcdl/msb/ConsumerTest.java | 120 +++++++++++-------
 .../msb/collector/CollectorManagerTest.java   |  16 ++-
 ...leMessageHandlerInvokeAdapterImplTest.java |  33 +++++
 .../SimpleMessageHandlerResolverImplTest.java |  35 +++++
 pom.xml                                       |   6 +
 29 files changed, 546 insertions(+), 158 deletions(-)
 create mode 100644 amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapter.java
 create mode 100644 amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapterTest.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/MessageHandlerResolver.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeAdapter.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImpl.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImpl.java
 create mode 100644 core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImplTest.java
 create mode 100644 core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImplTest.java

diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
index 75d0a386..44eb5911 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
@@ -19,6 +19,7 @@ public class ConfigurationSteps extends MsbSteps {
     private String MSB_BROKER_CONFIG_ROOT = "msbConfig.brokerConfig";
     private String MSB_BROKER_CONSUMER_THREAD_POOL_SIZE = MSB_BROKER_CONFIG_ROOT + ".consumerThreadPoolSize";
     private String MSB_BROKER_CONSUMER_THREAD_POOL_QUEUE_CAPACITY = MSB_BROKER_CONFIG_ROOT + ".consumerThreadPoolQueueCapacity";
+    private String MSB_BROKER_CONSUMER_THREAD_POOL_PREFETCH_COUNT = MSB_BROKER_CONFIG_ROOT + ".prefetchCount";
 
     private Config config = ConfigFactory.load();
 
@@ -42,6 +43,12 @@ public void initWithConsumerThreadPoolQueueCapacity(int capacity) {
         config = config.withValue(MSB_BROKER_CONSUMER_THREAD_POOL_QUEUE_CAPACITY, ConfigValueFactory.fromAnyRef(capacity));
     }
 
+    @Given("MSB configuration with consumer prefetch count $count")
+    public void initWithConsumerPrefetchCount(int capacity) {
+        config = config.withValue(MSB_BROKER_CONSUMER_THREAD_POOL_PREFETCH_COUNT, ConfigValueFactory.fromAnyRef(capacity));
+    }
+
+
     @Given("start MSB")
     public void initMSB() {
         helper.initWithConfig(config);
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 99768b28..c4e6e8fb 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -8,8 +8,10 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.hamcrest.Matchers;
 import org.jbehave.core.annotations.Given;
@@ -26,14 +28,19 @@
  */
 public class RequesterResponderSteps extends MsbSteps {
 
-    private Requester requester;
-    private String responseBody;
-    private Map receivedResponse;
-    private CompletableFuture> receivedResponseFuture;
-    private int countRequestsReceived = 0;
-    private Optional nextRequestAckType = Optional.empty();
-    private Optional defaultRequestsAckType = Optional.empty();
-    private boolean isResponseInNewThread = false;
+    private volatile Requester requester;
+    private volatile String responseBody;
+    private volatile Map receivedResponse;
+    private volatile CompletableFuture> receivedResponseFuture;
+    private volatile CountDownLatch responseCountDown;
+    private volatile AtomicInteger countRequestsReceived;
+    private volatile AtomicInteger countResponsesReceived;
+    private volatile Optional nextRequestAckType = Optional.empty();
+    private volatile Optional defaultRequestsAckType = Optional.empty();
+    private volatile boolean isResponseInNewThread = false;
+    private volatile int responseProcessingDelay;
+    private volatile int responsesToSendCount;
+    private volatile int responsesToExpectCount;
 
     public Optional getDefaultRequestsAckType() {
         return defaultRequestsAckType;
@@ -48,14 +55,11 @@ public void createResponderServer(String namespace) {
     @Given("responder server from $contextName listens on namespace $namespace")
     @When("responder server from $contextName listens on namespace $namespace")
     public void createResponderServer(String contextName, String namespace) {
+        beforeCreateResponder();
         ObjectMapper mapper = helper.getPayloadMapper(contextName);
-        countRequestsReceived = 0;
-        nextRequestAckType = Optional.empty();
-        defaultRequestsAckType = Optional.empty();
-        isResponseInNewThread = false;
         helper.createResponderServer(contextName, namespace, (request, responderContext) -> {
             if (responseBody != null) {
-                countRequestsReceived++;
+                countRequestsReceived.incrementAndGet();
 
                 Runnable responseActions = () -> {
                     boolean isSendResponse = true;
@@ -83,7 +87,9 @@ public void createResponderServer(String contextName, String namespace) {
                         RestPayload payload = new RestPayload.Builder()
                                 .withBody(Utils.fromJson(responseBody, Map.class, mapper))
                                 .build();
-                        responderContext.getResponder().send(payload);
+                        for(int i=0; i< responsesToSendCount; i++) {
+                            responderContext.getResponder().send(payload);
+                        }
                     }
                 };
 
@@ -97,6 +103,18 @@ public void createResponderServer(String contextName, String namespace) {
         }).listen();
     }
 
+    private void beforeCreateResponder() {
+        responseCountDown = null;
+        countRequestsReceived = new AtomicInteger(0);
+        countResponsesReceived = new AtomicInteger(0);
+        responseProcessingDelay = 0;
+        responsesToSendCount = 1;
+        responsesToExpectCount = 1;
+        nextRequestAckType = Optional.empty();
+        defaultRequestsAckType = Optional.empty();
+        isResponseInNewThread = false;
+    }
+
     @Given("responder server will $nextRequestAckType next request")
     public void setNextRequestAckType(String nextRequestAckType) throws Exception {
         this.nextRequestAckType = Optional.of(nextRequestAckType);
@@ -107,6 +125,16 @@ public void setDefaultRequestsAckType(String allRequestsAckType) throws Exceptio
         this.defaultRequestsAckType = Optional.of(allRequestsAckType);
     }
 
+    @Given("requester will process responses with $timeout ms delay")
+    public void setesponseDelay(int responseDelay) throws Exception {
+        this.responseProcessingDelay = responseDelay;
+    }
+
+    @Given("responder will provide $responseCount responses")
+    public void setResponsesToSendCount(int responsesToSendCount) throws Exception {
+        this.responsesToSendCount = responsesToSendCount;
+    }
+
     @Given("responder server will send acknowledge and response from a new thread")
     public void setResponseInNewThread() throws Exception {
         isResponseInNewThread = true;
@@ -124,6 +152,14 @@ public void createRequester(String namespace) {
         createRequester(DEFAULT_CONTEXT_NAME, namespace);
     }
 
+    // requester steps
+    @Given("requester (with $requestTimeout ms request timeout to receive $responseCount responses) sends requests to namespace $namespace")
+    public void createRequester(int requestTimeout, int responseCount, String namespace) {
+        responseCountDown = new CountDownLatch(responseCount);
+        responsesToExpectCount = responseCount;
+        requester = helper.createRequester(DEFAULT_CONTEXT_NAME, namespace, responsesToExpectCount, 100, requestTimeout, RestPayload.class);
+    }
+
     @Given("requester from $contextName sends requests to namespace $namespace")
     public void createRequester(String contextName, String namespace) {
         requester = helper.createRequester(contextName, namespace, 1, RestPayload.class);
@@ -138,21 +174,21 @@ public void sendRequest() throws Exception {
     public void sendRequest(String contextName) throws Exception {
         onBeforeRequest();
         RestPayload payload = helper.createFacetParserPayload("QUERY", null);
-        helper.sendRequest(requester, payload, 1, this::onResponse);
+        helper.sendRequest(requester, payload, responsesToExpectCount, this::onResponse);
     }
 
     @When("requester sends a request with query '$query'")
     public void sendRequestWithQuery(String query) throws Exception {
         onBeforeRequest();
         RestPayload payload = helper.createFacetParserPayload(query, null);
-        helper.sendRequest(requester, payload, true, 1, null, this::onResponse);
+        helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse);
     }
 
     @When("requester sends a request with body '$body'")
     public void sendRequestWithBody(String body) throws Exception {
         onBeforeRequest();
         RestPayload payload = helper.createFacetParserPayload(null, body);
-        helper.sendRequest(requester, payload, true, 1, null, this::onResponse);
+        helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse);
     }
 
     private void onBeforeRequest() {
@@ -161,6 +197,19 @@ private void onBeforeRequest() {
     }
 
     private void onResponse(RestPayload> payload) {
+        if(responseProcessingDelay > 0) {
+            try {
+                Thread.sleep(responseProcessingDelay);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        if(responseCountDown != null) {
+            responseCountDown.countDown();
+        }
+
+        countResponsesReceived.incrementAndGet();
         receivedResponseFuture.complete(payload.getBody());
     }
 
@@ -174,6 +223,13 @@ public void waitForResponse(long timeout) throws Exception {
         Assert.assertNotNull("Response received is null", receivedResponse);
     }
 
+    @Then("requester will get all responses in $timeout ms")
+    public void waitForAllResponses(long timeout) throws Exception {
+        if(!responseCountDown.await(timeout, TimeUnit.MILLISECONDS)) {
+            Assert.fail("All responses has not been received during a timeout, pending count: " + responseCountDown.getCount());
+        }
+    }
+
     @Then("requester does not get a response in $timeout ms")
     public void waitForNoResponse(long timeout) throws Exception {
         try {
@@ -198,7 +254,12 @@ public void responseEquals(ExamplesTable table) throws Exception {
 
     @Then("responder requests received count equals $expectedRequestsReceivedCount")
     public void requestCountEquals(int expectedCountRequestsReceived) throws Exception {
-        Assert.assertEquals(expectedCountRequestsReceived, countRequestsReceived);
+        Assert.assertEquals(expectedCountRequestsReceived, countRequestsReceived.get());
+    }
+
+    @Then("requester responses received count equals $expectedRequestsReceivedCount")
+    public void responseCountEquals(int expectedCountResponsesReceived) throws Exception {
+        Assert.assertEquals(expectedCountResponsesReceived, countResponsesReceived.get());
     }
 
     @Then("response contains $table")
diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story
index b7830a35..06be4db8 100644
--- a/acceptance/src/test/resources/scenarios/requester_responder.story
+++ b/acceptance/src/test/resources/scenarios/requester_responder.story
@@ -1,6 +1,7 @@
 Lifecycle:
 Before:
-Given start MSB
+Given MSB configuration with consumer prefetch count 20
+And start MSB
 After:
 Outcome: ANY
 Then shutdown MSB
@@ -95,4 +96,16 @@ When requester sends a request
 Then requester does not get a response in 1000 ms
 And responder requests received count equals 2
 
+Scenario: Requester processes callback with a delay
+
+Given responder server responds with '{"result": "hello jbehave"}'
+And responder server listens on namespace test:jbehave
+And responder will provide 10 responses
+And requester (with 1000 ms request timeout to receive 10 responses) sends requests to namespace test:jbehave
+And requester will process responses with 200 ms delay
+When requester sends a request
+Then requester will get all responses in 5000 ms
+And responder requests received count equals 1
+
+
 
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 3d0e1bb3..87a7b64e 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -26,6 +26,11 @@
             io.github.tcdl.msb
             msb-java-core
         
+        
+            io.github.tcdl.msb
+            msb-java-core
+            test-jar
+        
         
             com.rabbitmq
             amqp-client
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
index 75578a17..bdeafcd9 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
@@ -7,6 +7,7 @@
 import com.typesafe.config.ConfigFactory;
 import io.github.tcdl.msb.adapters.AdapterFactory;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
@@ -74,7 +75,7 @@ public ProducerAdapter createProducerAdapter(String topic) {
 
     @Override
     public ConsumerAdapter createConsumerAdapter(String topic) {
-        return new AmqpConsumerAdapter(topic, amqpBrokerConfig, connectionManager, consumerThreadPool);
+        return new AmqpConsumerAdapter(topic, amqpBrokerConfig, connectionManager);
     }
 
     protected ConnectionFactory createConnectionFactory(AmqpBrokerConfig adapterConfig) {
@@ -173,6 +174,11 @@ protected ExecutorService createConsumerThreadPool(AmqpBrokerConfig amqpBrokerCo
                 threadFactory);
     }
 
+    @Override
+    public MessageHandlerInvokeAdapter createMessageHandlerInvokeAdapter(String topic) {
+        return new AmqpMessageHandlerInvokeAdapter(consumerThreadPool);
+    }
+
     AmqpBrokerConfig getAmqpBrokerConfig() {
         return amqpBrokerConfig;
     }
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
index a47eed2b..945daa32 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
@@ -19,23 +19,18 @@ public class AmqpConsumerAdapter implements ConsumerAdapter {
     private String exchangeName;
     private String consumerTag;
     private AmqpBrokerConfig adapterConfig;
-    private ExecutorService consumerThreadPool;
 
     /**
      * The constructor.
      * @param topic - a topic name associated with the adapter
-     * @param consumerThreadPool contains incoming messages wrapped as tasks for further processing. Parameters of this thread pool determine degree of
-     *                           parallelism of incoming message processing
      * @throws ChannelException if some problems during setup channel from RabbitMQ connection were occurred
      */
-
-    public AmqpConsumerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager, ExecutorService consumerThreadPool) {
+    public AmqpConsumerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
         Validate.notNull(topic, "the 'topic' must not be null");
 
         this.topic = topic;
         this.exchangeName = topic;
         this.adapterConfig = amqpBrokerConfig;
-        this.consumerThreadPool = consumerThreadPool;
 
         try {
             channel = connectionManager.obtainConnection().createChannel();
@@ -61,7 +56,7 @@ public void subscribe(RawMessageHandler msgHandler) {
             channel.basicQos(prefetchCount); // Don't accept more messages if we have any unacknowledged
             channel.queueBind(queueName, exchangeName, "");
 
-            consumerTag = channel.basicConsume(queueName, false /* autoAck */, new AmqpMessageConsumer(channel, consumerThreadPool, msgHandler, adapterConfig));
+            consumerTag = channel.basicConsume(queueName, false /* autoAck */, new AmqpMessageConsumer(channel, msgHandler, adapterConfig));
         } catch (IOException e) {
             throw new ChannelException(String.format("Failed to subscribe to topic %s", topic), e);
         }
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
index a5520f1e..b8db3052 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
@@ -32,13 +32,11 @@ public class AmqpMessageConsumer extends DefaultConsumer {
 
     private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageConsumer.class);
 
-    ExecutorService consumerThreadPool;
     ConsumerAdapter.RawMessageHandler msgHandler;
     private AmqpBrokerConfig amqpBrokerConfig;
 
-    public AmqpMessageConsumer(Channel channel, ExecutorService consumerThreadPool, ConsumerAdapter.RawMessageHandler msgHandler, AmqpBrokerConfig amqpBrokerConfig) {
+    public AmqpMessageConsumer(Channel channel, ConsumerAdapter.RawMessageHandler msgHandler, AmqpBrokerConfig amqpBrokerConfig) {
         super(channel);
-        this.consumerThreadPool = consumerThreadPool;
         this.msgHandler = msgHandler;
         this.amqpBrokerConfig = amqpBrokerConfig;
     }
@@ -56,11 +54,11 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp
             LOG.debug(String.format("[consumer tag: %s] Message consumed from broker: %s", consumerTag, bodyStr));
 
             try {
-                consumerThreadPool.submit(new AmqpMessageProcessingTask(consumerTag, bodyStr, msgHandler, ackHandler));
-                LOG.debug(String.format("[consumer tag: %s] Message has been put in the processing queue: %s.",
+                msgHandler.onMessage(bodyStr, ackHandler);
+                LOG.debug(String.format("[consumer tag: %s] Raw message has been handled: %s.",
                         consumerTag, bodyStr));
             } catch (Exception e) {
-                LOG.error(String.format("[consumer tag: %s] Couldn't put message in the processing queue: %s.",
+                LOG.error(String.format("[consumer tag: %s] Can't handle a raw message: %s.",
                         consumerTag, bodyStr), e);
                 throw e;
             }
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapter.java
new file mode 100644
index 00000000..4ea53ebb
--- /dev/null
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapter.java
@@ -0,0 +1,35 @@
+package io.github.tcdl.msb.adapters.amqp;
+
+import io.github.tcdl.msb.MessageHandler;
+import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
+import io.github.tcdl.msb.api.message.Message;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ExecutorService;
+
+/**
+ * {@link MessageHandlerInvokeAdapter} implementation that preforms a an {@link MessageHandler} invocation
+ * in a separate thread.
+ */
+public class AmqpMessageHandlerInvokeAdapter implements MessageHandlerInvokeAdapter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageHandlerInvokeAdapter.class);
+
+    private final ExecutorService consumerThreadPool;
+
+    /**
+     * @param consumerThreadPool thread pool to be used for {@link MessageHandler} invocations.
+     */
+    public AmqpMessageHandlerInvokeAdapter(ExecutorService consumerThreadPool) {
+        this.consumerThreadPool = consumerThreadPool;
+    }
+
+    @Override
+    public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) {
+        consumerThreadPool.submit(new AmqpMessageProcessingTask(messageHandler, message, acknowledgeHandler));
+        LOG.debug(String.format("[correlation id: %s] Message has been put in the processing queue.",
+                message.getCorrelationId()));
+    }
+}
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java
index 2593ede5..1f67e703 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java
@@ -1,7 +1,9 @@
 package io.github.tcdl.msb.adapters.amqp;
 
+import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.api.message.Message;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -11,16 +13,14 @@
 public class AmqpMessageProcessingTask implements Runnable {
     private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageProcessingTask.class);
 
-    final String consumerTag;
-    final String body;
-    final ConsumerAdapter.RawMessageHandler msgHandler;
+    final Message message;
+    final MessageHandler messageHandler;
     final AcknowledgementHandlerInternal ackHandler;
 
-    public AmqpMessageProcessingTask(String consumerTag, String body, ConsumerAdapter.RawMessageHandler msgHandler,
+    public AmqpMessageProcessingTask( MessageHandler messageHandler, Message message,
                                      AcknowledgementHandlerInternal ackHandler) {
-        this.consumerTag = consumerTag;
-        this.body = body;
-        this.msgHandler = msgHandler;
+        this.message = message;
+        this.messageHandler = messageHandler;
         this.ackHandler = ackHandler;
     }
 
@@ -32,12 +32,12 @@ public AmqpMessageProcessingTask(String consumerTag, String body, ConsumerAdapte
     @Override
     public void run() {
         try {
-            LOG.debug(String.format("[consumer tag: %s] Starting message processing: %s", consumerTag, body));
-            msgHandler.onMessage(body, ackHandler);
-            LOG.debug(String.format("[consumer tag: %s] Message has been processed: %s", consumerTag, body));
+            LOG.debug(String.format("[correlation id: %s] Starting message processing", message.getCorrelationId()));
+            messageHandler.handleMessage(message, ackHandler);
+            LOG.debug(String.format("[correlation id: %s] Message has been processed", message.getCorrelationId()));
             ackHandler.autoConfirm();
         } catch (Exception e) {
-            LOG.error(String.format("[consumer tag: %s] Failed to process message %s", consumerTag, body), e);
+            LOG.error(String.format("[correlation id: %s] Failed to process message", message.getCorrelationId()), e);
             ackHandler.autoRetry();
         }
     }
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
index eb4d5cab..b9eae19b 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
@@ -1,15 +1,15 @@
 package io.github.tcdl.msb.adapters.amqp;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.withSettings;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
@@ -25,10 +25,13 @@
 
 import com.rabbitmq.client.Connection;
 import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.Recoverable;
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
 
+@RunWith(MockitoJUnitRunner.class)
 public class AmqpAdapterFactoryTest {
     final Charset charset = Charset.forName("UTF-8");
     final String host = "127.0.0.1";
@@ -44,13 +47,21 @@ public class AmqpAdapterFactoryTest {
     final int heartbeatIntervalSec = 1;
     final long networkRecoveryIntervalMs = 5000;
     final int prefetchCount = 1;
-    
-    AmqpBrokerConfig amqpConfig;
-    AmqpAdapterFactory amqpAdapterFactory;
+
+    @Mock
     AmqpConnectionManager mockConnectionManager;
+
+    @Mock
     ConnectionFactory mockConnectionFactory;
+
+    @Mock
     Connection mockConnection;
+
+    @Mock
     ExecutorService mockConsumerThreadPool;
+
+    AmqpBrokerConfig amqpConfig;
+    AmqpAdapterFactory amqpAdapterFactory;
     MsbConfig msbConfigurations;
     
     @Before
@@ -68,11 +79,6 @@ public void setUp() {
         Config msbConfig = ConfigFactory.parseString(configStr);
         msbConfigurations = new MsbConfig(msbConfig); 
 
-        mockConnectionFactory = mock(ConnectionFactory.class);
-        mockConnection = mock(Connection.class, withSettings().extraInterfaces(Recoverable.class));
-        mockConnectionManager = mock(AmqpConnectionManager.class);
-        mockConsumerThreadPool = mock(ExecutorService.class);
-        
         //Define conditions for ExecutorService termination
         try {
             when(mockConsumerThreadPool.awaitTermination(anyInt(), any(TimeUnit.class))).thenReturn(true);
@@ -93,7 +99,7 @@ public ProducerAdapter createProducerAdapter(String topic) {
 
             @Override
             public ConsumerAdapter createConsumerAdapter(String topic) {
-                return new AmqpConsumerAdapter(topic, amqpConfig, mockConnectionManager, mockConsumerThreadPool);
+                return new AmqpConsumerAdapter(topic, amqpConfig, mockConnectionManager);
             }
 
             @Override
@@ -147,6 +153,12 @@ public void testInitGroupIdWithServiceName() {
         assertEquals(amqpBrokerConfig.getGroupId().get(), msbConfigurations.getServiceDetails().getName());
     }
 
+    @Test
+    public void testCreateMessageHandlerInvokeAdapter() {
+        MessageHandlerInvokeAdapter adapter = amqpAdapterFactory.createMessageHandlerInvokeAdapter("any");
+        assertTrue(adapter instanceof AmqpMessageHandlerInvokeAdapter);
+    }
+
     @Test
     public void testShutdown() {
         amqpAdapterFactory.init(msbConfigurations);
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
index 8aed9ba5..3a6d35d7 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
@@ -28,7 +28,7 @@ public class AmqpConsumerAdapterTest {
 
     private Channel mockChannel;
     private AmqpConnectionManager mockAmqpConnectionManager;
-    private ExecutorService mockConsumerThreadPool;
+
 
     @Before
     public void setUp() throws Exception {
@@ -38,8 +38,6 @@ public void setUp() throws Exception {
         
         when(mockAmqpConnectionManager.obtainConnection()).thenReturn(mockConnection);
         when(mockConnection.createChannel()).thenReturn(mockChannel);
-
-        mockConsumerThreadPool = mock(ExecutorService.class);
     }
 
     @Test
@@ -107,7 +105,6 @@ public void testRegisteredHandlerInvoked() throws IOException {
 
         AmqpMessageConsumer consumer = amqpConsumerCaptor.getValue();
         assertEquals(mockChannel, consumer.getChannel());
-        assertEquals(mockConsumerThreadPool, consumer.consumerThreadPool);
         assertEquals(mockHandler, consumer.msgHandler);
     }
 
@@ -127,6 +124,6 @@ public void testUnsubscribe() throws IOException {
     private AmqpConsumerAdapter createAdapter(String topic, String groupId, boolean durable) {
         AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
                 false, Optional.of(groupId), durable, 5, 20, 1, 5000, 1);
-        return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, mockConsumerThreadPool);
+        return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager);
     }
 }
\ No newline at end of file
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java
index 334592d1..8bcc1baf 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java
@@ -32,9 +32,6 @@ public class AmqpMessageConsumerTest {
     @Mock
     private Channel mockChannel;
 
-    @Mock
-    private ExecutorService mockExecutorService;
-
     @Mock
     private ConsumerAdapter.RawMessageHandler mockMessageHandler;
 
@@ -50,7 +47,7 @@ public class AmqpMessageConsumerTest {
     public void setUp() {
         when(mockBrokerConfig.getCharset()).thenReturn(Charset.forName("UTF-8"));
 
-        amqpMessageConsumer = new AmqpMessageConsumer(mockChannel, mockExecutorService, mockMessageHandler, mockBrokerConfig) {
+        amqpMessageConsumer = new AmqpMessageConsumer(mockChannel, mockMessageHandler, mockBrokerConfig) {
             @Override
             AcknowledgementHandlerImpl createAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) {
                 return amqpAcknowledgementHandler;
@@ -69,15 +66,8 @@ public void testMessageProcessing() throws IOException {
         // method under test
         amqpMessageConsumer.handleDelivery(consumerTag, envelope, null, messageStr.getBytes());
 
-        // verify that a new task has been submitted
-        ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AmqpMessageProcessingTask.class);
-        verify(mockExecutorService).submit(taskCaptor.capture());
+        verify(mockMessageHandler, times(1)).onMessage(eq(messageStr), eq(amqpAcknowledgementHandler));
 
-        // verify that the right task was submitted
-        AmqpMessageProcessingTask task = taskCaptor.getValue();
-        assertEquals(consumerTag, task.consumerTag);
-        assertEquals(messageStr, task.body);
-        assertEquals(mockMessageHandler, task.msgHandler);        
     }
 
     @Test
@@ -86,7 +76,7 @@ public void testMessageCannotBeSubmittedForProcessing() throws IOException {
         Envelope envelope = mock(Envelope.class);
         when(envelope.getDeliveryTag()).thenReturn(deliveryTag);
 
-        doThrow(new RejectedExecutionException()).when(mockExecutorService).submit(any(Runnable.class));
+        doThrow(new RejectedExecutionException()).when(mockMessageHandler).onMessage(anyString(), any());
 
         try {
             amqpMessageConsumer.handleDelivery("consumer tag", envelope, null, "some message".getBytes());
@@ -119,7 +109,7 @@ public void testRejectFailed() throws IOException {
         Envelope envelope = mock(Envelope.class);
         when(envelope.getDeliveryTag()).thenReturn(deliveryTag);
 
-        doThrow(new RejectedExecutionException()).when(mockExecutorService).submit(any(Runnable.class));
+        doThrow(new RejectedExecutionException()).when(mockMessageHandler).onMessage(anyString(), any());
         doThrow(new RuntimeException()).when(mockChannel).basicReject(eq(deliveryTag), anyBoolean());
 
         try {
@@ -140,12 +130,11 @@ public void testProperCharsetUsed() throws IOException {
         Envelope envelope = mock(Envelope.class);
         when(envelope.getDeliveryTag()).thenReturn(1234L);
 
-        AmqpMessageConsumer consumer = new AmqpMessageConsumer(mockChannel, mockExecutorService, mockMessageHandler, mockBrokerConfig);
+        AmqpMessageConsumer consumer = new AmqpMessageConsumer(mockChannel, mockMessageHandler, mockBrokerConfig);
         consumer.handleDelivery("some tag", envelope, null, encodedMessage);
 
-        ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AmqpMessageProcessingTask.class);
-        verify(mockExecutorService).submit(taskCaptor.capture());
-        assertEquals(expectedDecodedMessage, taskCaptor.getValue().body);
+        verify(mockMessageHandler, times(1)).onMessage(eq(expectedDecodedMessage), any());
+
     }
 
 }
\ No newline at end of file
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapterTest.java
new file mode 100644
index 00000000..bc3fb93f
--- /dev/null
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapterTest.java
@@ -0,0 +1,47 @@
+package io.github.tcdl.msb.adapters.amqp;
+
+import io.github.tcdl.msb.MessageHandler;
+import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.api.message.Message;
+import io.github.tcdl.msb.support.TestUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.concurrent.ExecutorService;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AmqpMessageHandlerInvokeAdapterTest {
+    @Mock
+    ExecutorService mockExecutor;
+
+    @Mock
+    AcknowledgementHandlerInternal acknowledgeHandler;
+
+    @Mock
+    MessageHandler messageHandler;
+
+    Message message = TestUtils.createMsbRequestMessage("any","any");
+
+    @InjectMocks
+    AmqpMessageHandlerInvokeAdapter adapter;
+
+    @Test
+    public void testMessageHandling() {
+        adapter.execute(messageHandler, message, acknowledgeHandler);
+        verify(messageHandler, never()).handleMessage(any(), any());
+        ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AmqpMessageProcessingTask.class);
+        verify(mockExecutor, times(1)).submit(taskCaptor.capture());
+        AmqpMessageProcessingTask task = taskCaptor.getValue();
+
+        assertEquals(message, task.message);
+        assertEquals(messageHandler, task.messageHandler);
+        assertEquals(acknowledgeHandler, task.ackHandler);
+    }
+}
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java
index 4782eeab..eca8f66b 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java
@@ -4,13 +4,17 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.*;
+import io.github.tcdl.msb.support.TestUtils;
 
+import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 
 import java.io.IOException;
 
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl;
+import io.github.tcdl.msb.api.message.Message;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import com.rabbitmq.client.Channel;
@@ -23,11 +27,12 @@ public class AmqpMessageProcessingTaskTest {
 
     private String messageStr = "some message";
 
+    private Message message;
     @Mock
     private Channel mockChannel;
 
     @Mock
-    private ConsumerAdapter.RawMessageHandler mockMessageHandler;
+    private MessageHandler mockMessageHandler;
 
     @Mock
     private AcknowledgementHandlerImpl mockAcknowledgementHandler;
@@ -36,20 +41,21 @@ public class AmqpMessageProcessingTaskTest {
 
     @Before
     public void setUp() {
-        task = new AmqpMessageProcessingTask("consumer tag", messageStr, mockMessageHandler, mockAcknowledgementHandler);
+        message = TestUtils.createSimpleRequestMessage("any");
+        task = new AmqpMessageProcessingTask(mockMessageHandler, message, mockAcknowledgementHandler);
     }
 
     @Test
     public void testMessageProcessing() throws IOException {
         task.run();
-        verify(mockMessageHandler).onMessage(messageStr, mockAcknowledgementHandler);
+        verify(mockMessageHandler).handleMessage(any(), eq(mockAcknowledgementHandler));
         verify(mockAcknowledgementHandler, times(1)).autoConfirm();
         verifyNoMoreInteractions(mockAcknowledgementHandler);
     }
 
     @Test
     public void testExceptionDuringProcessing() {
-        doThrow(new RuntimeException()).when(mockMessageHandler).onMessage(anyString(), any());
+        doThrow(new RuntimeException()).when(mockMessageHandler).handleMessage(any(), any());
 
         try {
             task.run();
diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
index c5c07e69..675fae95 100644
--- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
@@ -5,14 +5,12 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import io.github.tcdl.msb.adapters.AdapterFactory;
-import io.github.tcdl.msb.adapters.AdapterFactoryLoader;
-import io.github.tcdl.msb.adapters.ConsumerAdapter;
-import io.github.tcdl.msb.adapters.ProducerAdapter;
+import io.github.tcdl.msb.adapters.*;
 import io.github.tcdl.msb.api.Callback;
 import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.api.message.Message;
+import io.github.tcdl.msb.impl.SimpleMessageHandlerResolverImpl;
 import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent;
 import io.github.tcdl.msb.monitor.agent.NoopChannelMonitorAgent;
 import io.github.tcdl.msb.support.JsonValidator;
@@ -70,12 +68,16 @@ public Producer findOrCreateProducer(final String topic) {
      * @throws ConsumerSubscriptionException if subscriber for topic already exist
      */
     public synchronized boolean subscribe(String topic, MessageHandler messageHandler) {
+        return subscribe(topic, new SimpleMessageHandlerResolverImpl(messageHandler));
+    }
+
+    public synchronized boolean subscribe(String topic, MessageHandlerResolver messageHandlerResolver) {
         Validate.notNull(topic, "field 'topic' is null");
-        Validate.notNull(messageHandler, "field 'messageHandler' is null");
+        Validate.notNull(messageHandlerResolver, "field 'messageHandlerResolver' is null");
         if (consumersByTopic.get(topic) != null) {
             throw new ConsumerSubscriptionException("Subscriber for this topic: " + topic + " already exist");
         } else {
-            Consumer newConsumer = createConsumer(topic, messageHandler);
+            Consumer newConsumer = createConsumer(topic, messageHandlerResolver);
             channelMonitorAgent.consumerTopicCreated(topic);
             consumersByTopic.put(topic, newConsumer);
             return false;
@@ -104,12 +106,12 @@ private Producer createProducer(String topic) {
         return new Producer(adapter, topic, handler, messageMapper);
     }
 
-    private Consumer createConsumer(String topic, MessageHandler messageHandler) {
+    private Consumer createConsumer(String topic, MessageHandlerResolver messageHandlerResolver) {
         Utils.validateTopic(topic);
 
         ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic);
-
-        return new Consumer(adapter, topic, messageHandler, msbConfig, clock, channelMonitorAgent, validator, messageMapper);
+        MessageHandlerInvokeAdapter invokeAdapter = getAdapterFactory().createMessageHandlerInvokeAdapter(topic);
+        return new Consumer(adapter, invokeAdapter, topic, messageHandlerResolver, msbConfig, clock, channelMonitorAgent, validator, messageMapper);
     }
 
     public void shutdown() {
diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java
index a2106ef6..0b84d9b9 100644
--- a/core/src/main/java/io/github/tcdl/msb/Consumer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java
@@ -2,6 +2,7 @@
 
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.MetaMessage;
 import io.github.tcdl.msb.config.MsbConfig;
@@ -12,6 +13,7 @@
 import java.time.Clock;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
+import java.util.Optional;
 
 import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
@@ -27,31 +29,33 @@ public class Consumer {
     private static final Logger LOG = LoggerFactory.getLogger(Consumer.class);
 
     private final ConsumerAdapter rawAdapter;
+    private final MessageHandlerInvokeAdapter messageHandlerInvokeAdapter;
     private final String topic;
     private MsbConfig msbConfig;
     private ChannelMonitorAgent channelMonitorAgent;
     private Clock clock;
-    private MessageHandler messageHandler;
+    private MessageHandlerResolver messageHandlerResolver;
     private JsonValidator validator;
     private ObjectMapper messageMapper;
 
     /**
      * @param rawAdapter instance of {@link ConsumerAdapter} that allows to receive messages from message bus
      * @param topic
-     * @param messageHandler interface that user can implement to handle received message
+     * @param messageHandlerResolver resolves {@link MessageHandler} instance that user can implement to handle received messages.
      * @param msbConfig consumer configs
      * @param clock
      * @param channelMonitorAgent
      * @param validator validates incoming messages
      * @param messageMapper message deserializer
      */
-    public Consumer(ConsumerAdapter rawAdapter, String topic, MessageHandler messageHandler, MsbConfig msbConfig,
+    public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvokeAdapter messageHandlerInvokeAdapter,
+            String topic, MessageHandlerResolver messageHandlerResolver, MsbConfig msbConfig,
             Clock clock, ChannelMonitorAgent channelMonitorAgent, JsonValidator validator, ObjectMapper messageMapper) {
 
         LOG.debug("Creating consumer for topic: {}", topic);
         Validate.notNull(rawAdapter, "the 'rawAdapter' must not be null");
         Validate.notNull(topic, "the 'topic' must not be null");
-        Validate.notNull(messageHandler, "the 'messageHandler' must not be null");
+        Validate.notNull(messageHandlerResolver, "the 'messageHandlerResolver' must not be null");
         Validate.notNull(msbConfig, "the 'msbConfig' must not be null");
         Validate.notNull(clock, "the 'clock' must not be null");
         Validate.notNull(channelMonitorAgent, "the 'channelMonitorAgent' must not be null");
@@ -59,8 +63,9 @@ public Consumer(ConsumerAdapter rawAdapter, String topic, MessageHandler message
         Validate.notNull(messageMapper, "the 'messageMapper' must not be null");
 
         this.rawAdapter = rawAdapter;
+        this.messageHandlerInvokeAdapter = messageHandlerInvokeAdapter;
         this.topic = topic;
-        this.messageHandler = messageHandler;
+        this.messageHandlerResolver = messageHandlerResolver;
         this.msbConfig = msbConfig;
         this.clock = clock;
         this.channelMonitorAgent = channelMonitorAgent;
@@ -104,7 +109,13 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern
         }
 
         try {
-            messageHandler.handleMessage(message, acknowledgeHandler);
+            Optional messageHandler = messageHandlerResolver.resolveMessageHandler(message);
+            if(messageHandler.isPresent()) {
+                messageHandlerInvokeAdapter.execute(messageHandler.get(), message, acknowledgeHandler);
+            } else {
+                LOG.warn("Cant't resolve message handler for a message: {}", jsonMessage);
+                acknowledgeHandler.autoReject();
+            }
         } catch (Exception e) {
             LOG.warn("Error while trying to handle a message: {}", jsonMessage, e);
             acknowledgeHandler.autoRetry();
diff --git a/core/src/main/java/io/github/tcdl/msb/MessageHandlerResolver.java b/core/src/main/java/io/github/tcdl/msb/MessageHandlerResolver.java
new file mode 100644
index 00000000..124e16fb
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/MessageHandlerResolver.java
@@ -0,0 +1,18 @@
+package io.github.tcdl.msb;
+
+import io.github.tcdl.msb.api.message.Message;
+
+import java.util.Optional;
+
+/**
+ * Implementations of this interface gives an ability to resolve {@link MessageHandler} by an incoming {@link Message}.
+ */
+public interface MessageHandlerResolver {
+
+    /**
+     * Resolve {@link MessageHandler} by an incoming {@link Message}.
+     * @param message
+     * @return
+     */
+    Optional resolveMessageHandler(Message message);
+}
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
index 77b65355..571705bc 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
@@ -32,6 +32,13 @@ public interface AdapterFactory {
      */
     ConsumerAdapter createConsumerAdapter(String topic);
 
+    /**
+     * Create {@link MessageHandlerInvokeAdapter} instance.
+     * @param topic topic name
+     * @return {@link MessageHandlerInvokeAdapter} instance associated with a topic.
+     */
+    MessageHandlerInvokeAdapter createMessageHandlerInvokeAdapter(String topic);
+
     /**
      * Closes all resources used by amqp producers and consumers. Should be called for graceful shutdown.
      */
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeAdapter.java
new file mode 100644
index 00000000..fb9bd426
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeAdapter.java
@@ -0,0 +1,18 @@
+package io.github.tcdl.msb.adapters;
+
+import io.github.tcdl.msb.MessageHandler;
+import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.api.message.Message;
+
+/**
+ * Interface that defines a way to invoke {@link MessageHandler} to process a {@link Message} received.
+ */
+public interface MessageHandlerInvokeAdapter {
+    /**
+     * Handle an incoming {@link Message} using {@link MessageHandler} provided.
+     * @param messageHandler {@link MessageHandler} instance related to a {@link Message} to be processed.
+     * @param message  {@link Message} to be processed.
+     * @param acknowledgeHandler acknowledgement handler.
+     */
+    void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler);
+}
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java
index 6ef10eb0..b0cc317a 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java
@@ -4,8 +4,10 @@
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutorService;
 
+import io.github.tcdl.msb.impl.SimpleMessageHandlerInvokeAdapterImpl;
 import io.github.tcdl.msb.adapters.AdapterFactory;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.support.Utils;
@@ -33,6 +35,11 @@ public ConsumerAdapter createConsumerAdapter(String topic) {
         return new MockAdapter(topic, consumerExecutors);
     }
 
+    @Override
+    public MessageHandlerInvokeAdapter createMessageHandlerInvokeAdapter(String topic) {
+        return new SimpleMessageHandlerInvokeAdapterImpl();
+    }
+
     @Override
     public void shutdown() {
         for (ExecutorService executorService : consumerExecutors) {
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
index 843ff952..cd128379 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
@@ -2,6 +2,7 @@
 
 import static io.github.tcdl.msb.support.Utils.ifNull;
 import static java.lang.Math.toIntExact;
+import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.api.AcknowledgementHandler;
 import io.github.tcdl.msb.api.Callback;
 import io.github.tcdl.msb.api.MessageContext;
@@ -34,7 +35,7 @@
 /**
  * {@link Collector} is a component which collects responses and acknowledgements for sent requests.
  */
-public class Collector {
+public class Collector implements MessageHandler {
 
     private static final Logger LOG = LoggerFactory.getLogger(Collector.class);
 
@@ -124,6 +125,7 @@ public void listenForResponses() {
         collectorManager.registerCollector(this);
     }
 
+    @Override
     public void handleMessage(Message incomingMessage, AcknowledgementHandler acknowledgeHandler) {
         LOG.debug("[correlation ids: {}-{}] Received {}",
                 requestMessage.getCorrelationId(), incomingMessage.getCorrelationId(), incomingMessage);
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java
index e71e53d5..03cadba4 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java
@@ -2,11 +2,13 @@
 
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.MessageHandler;
+import io.github.tcdl.msb.MessageHandlerResolver;
 import io.github.tcdl.msb.api.AcknowledgementHandler;
 import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException;
 import io.github.tcdl.msb.api.message.Message;
 
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.slf4j.Logger;
@@ -15,14 +17,14 @@
 /**
  * Manages instances of {@link Collector}s that listens to the same response topic.
  */
-public class CollectorManager implements MessageHandler {
+public class CollectorManager implements MessageHandlerResolver {
 
     private static final Logger LOG = LoggerFactory.getLogger(CollectorManager.class);
 
-    private boolean isSubscribed = false;
+    private volatile boolean isSubscribed = false;
 
-    private String topic;
-    private ChannelManager channelManager;
+    private final String topic;
+    private final ChannelManager channelManager;
     Map collectorsByCorrelationId = new ConcurrentHashMap<>();
 
     public CollectorManager(String topic, ChannelManager channelManager) {
@@ -31,18 +33,17 @@ public CollectorManager(String topic, ChannelManager channelManager) {
     }
 
     /**
-     * Determines correlationId from the incoming message and invokes the relevant {@link Collector} instance.
+     * Determines correlationId from the incoming message and resolves the relevant {@link Collector} instance.
      */
-    @Override
-    public void handleMessage(Message message, AcknowledgementHandler acknowledgeHandler) {
+    @Override public Optional resolveMessageHandler(Message message) {
         String correlationId = message.getCorrelationId();
         Collector collector = collectorsByCorrelationId.get(correlationId);
         if (collector != null) {
-            collector.handleMessage(message, acknowledgeHandler);
+            return Optional.of(collector);
         } else {
             LOG.warn("Message with correlationId {} is not expected to be processed by any Collectors", correlationId);
+            return Optional.empty();
         }
-
     }
 
     /**
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImpl.java
new file mode 100644
index 00000000..87cd1c98
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImpl.java
@@ -0,0 +1,17 @@
+package io.github.tcdl.msb.impl;
+
+import io.github.tcdl.msb.MessageHandler;
+import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
+import io.github.tcdl.msb.api.message.Message;
+
+/**
+ * Trivial {@link MessageHandlerInvokeAdapter} implementation that preforms a direct {@link MessageHandler} invocation
+ * to process a {@link Message} received.
+ */
+public class SimpleMessageHandlerInvokeAdapterImpl implements MessageHandlerInvokeAdapter {
+    @Override
+    public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) {
+        messageHandler.handleMessage(message, acknowledgeHandler);
+    }
+}
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImpl.java
new file mode 100644
index 00000000..c12ff9a0
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImpl.java
@@ -0,0 +1,24 @@
+package io.github.tcdl.msb.impl;
+
+import io.github.tcdl.msb.MessageHandler;
+import io.github.tcdl.msb.MessageHandlerResolver;
+import io.github.tcdl.msb.api.message.Message;
+
+import java.util.Optional;
+
+/**
+ * Trivial {@link MessageHandlerResolver} implementation that returns
+ * a single single  {@link MessageHandler} by any incoming {@link Message}.
+ */
+public class SimpleMessageHandlerResolverImpl implements MessageHandlerResolver {
+
+    private final MessageHandler messageHandler;
+
+    public SimpleMessageHandlerResolverImpl(MessageHandler messageHandler) {
+        this.messageHandler = messageHandler;
+    }
+
+    @Override public Optional resolveMessageHandler(Message message) {
+        return Optional.of(messageHandler);
+    }
+}
diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
index 7d0f0719..f7667c7a 100644
--- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
@@ -5,6 +5,7 @@
 
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
 import io.github.tcdl.msb.api.exception.JsonConversionException;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.MetaMessage;
@@ -18,7 +19,9 @@
 import java.time.Clock;
 import java.time.Instant;
 import java.time.ZoneId;
+import java.util.Optional;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -43,7 +46,13 @@ public class ConsumerTest {
 
     @Mock
     private MessageHandler messageHandlerMock;
-    
+
+    @Mock
+    private MessageHandlerResolver messageHandlerResolverMock;
+
+    @Mock
+    private MessageHandlerInvokeAdapter messageHandlerInvokeAdapterMock;
+
     @Mock
     private AcknowledgementHandlerInternal acknowledgementHandlerMock;
 
@@ -53,49 +62,55 @@ public class ConsumerTest {
 
     private ObjectMapper messageMapper = TestUtils.createMessageMapper();
 
+    @Before
+    public void setUp() {
+        when(messageHandlerResolverMock.resolveMessageHandler(any()))
+                .thenReturn(Optional.of(messageHandlerMock));
+    }
+
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullAdapter() {
-        new Consumer(null, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(null, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullTopic() {
-        new Consumer(adapterMock, null, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, null, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullMessageHandler() {
-        new Consumer(adapterMock, TOPIC, null, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, null, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullMsbConf() {
-        new Consumer(adapterMock, TOPIC, messageHandlerMock, null, clock, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, null, clock, channelMonitorAgentMock, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullClock() {
-        new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, null, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, null, channelMonitorAgentMock, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullMonitorAgent() {
-        new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, null, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, null, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullValidator() {
-        new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, null, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, null, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullMessageMapper() {
-        new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, null);
+        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, null);
     }
 
     @Test
     public void testSubscribeAdapterSubscribed() {
-        new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         verify(adapterMock).subscribe(any(ConsumerAdapter.RawMessageHandler.class));
     }
@@ -103,20 +118,46 @@ public void testSubscribeAdapterSubscribed() {
     @Test
     public void testValidMessageProcessedBySubscriber() throws JsonConversionException {
         Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
-        Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+
+        consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
+
+        verifyMessageHandled();
+    }
+
+    @Test
+    public void testMessageHandlerCantBeResolved() throws JsonConversionException {
+        when(messageHandlerResolverMock.resolveMessageHandler(any()))
+                .thenReturn(Optional.empty());
+
+        Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+
+        consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
+
+        verifyMessageNotHandled();
+        verify(acknowledgementHandlerMock, times(1)).autoReject();
+    }
+
+    @Test
+    public void testMessageHandlerInvokeException() throws JsonConversionException {
+        doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokeAdapterMock).execute(any(), any(), any());
+
+        Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
-        consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), null);
+        consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
 
-        verify(messageHandlerMock).handleMessage(any(Message.class), any());
+        verify(acknowledgementHandlerMock, times(1)).autoRetry();
     }
 
     @Test
     public void testExceptionWhileMessageConvertingProcessedBySubscriber() throws JsonConversionException {
-        Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock);
 
-        verify(messageHandlerMock, never()).handleMessage(any(Message.class), any());
+        verifyMessageNotHandled();
     }
 
     @Test
@@ -126,69 +167,53 @@ public void testHandleRawMessageConsumeFromTopicSkipValidation() {
         // disable validation
         when(msbConf.isValidateMessage()).thenReturn(false);
 
-        Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         // create a message with required empty namespace
         Message message = TestUtils.createSimpleRequestMessage("");
 
-        consumer.handleRawMessage(Utils.toJson(message, messageMapper), null);
+        consumer.handleRawMessage(Utils.toJson(message, messageMapper), acknowledgementHandlerMock);
 
         // should skip validation and process it
-        verify(messageHandlerMock).handleMessage(any(Message.class), any());
+        verifyMessageHandled();
     }
 
 
     @Test
     public void testHandleRawMessageConsumeFromTopic() throws JsonConversionException {
         Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
-        Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
-        consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), null);
-        verify(messageHandlerMock).handleMessage(any(Message.class), any());
+        consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
+        verifyMessageHandled();
     }
 
     @Test
     public void testHandleRawMessageConsumeFromTopicValidateThrowException() {
         MsbConfig msbConf = TestUtils.createMsbConfigurations();
-        Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock);
-        verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); // no processing
-        verify(acknowledgementHandlerMock, times(1)).autoReject();
+        verifyMessageNotHandled();
     }
 
     @Test
     public void testHandleRawMessageConsumeFromServiceTopicValidateThrowException() {
         String service_topic = "_service:topic";
         MsbConfig msbConf = TestUtils.createMsbConfigurations();
-        Consumer consumer = new Consumer(adapterMock, service_topic, messageHandlerMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, service_topic, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock);
-        verify(messageHandlerMock, never()).handleMessage(any(Message.class), any()); // no processing
+        verifyMessageNotHandled();
     }
 
     @Test
     public void testHandleRawMessageConsumeFromTopicExpiredMessage() throws JsonConversionException {
         Message expiredMessage = createExpiredMsbRequestMessageWithTopicTo(TOPIC);
-        Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper), acknowledgementHandlerMock);
-        verify(messageHandlerMock, never()).handleMessage(any(Message.class), any());
-        verify(acknowledgementHandlerMock, times(1)).autoReject();
-    }
-
-    @Test
-    public void testHandleMessageException() throws JsonConversionException {
-        Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
-        messageHandlerMock.handleMessage(any(Message.class), any());
-
-        doThrow(new RuntimeException()).when(messageHandlerMock).handleMessage(any(), any());
-
-        Consumer consumer = new Consumer(adapterMock, TOPIC, messageHandlerMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
-
-        consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
-        
-        verify(acknowledgementHandlerMock, times(1)).autoRetry();
+        verifyMessageNotHandled();
     }
 
     private  Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) {
@@ -202,5 +227,12 @@ private  Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) {
         return new Message.Builder().withCorrelationId(Utils.generateId()).withId(Utils.generateId()).withTopics(topic).withMetaBuilder(metaBuilder)
                 .build();
     }
-    
+
+    private void verifyMessageHandled() {
+        verify(messageHandlerInvokeAdapterMock, times(1)).execute(eq(messageHandlerMock), any(Message.class), eq(acknowledgementHandlerMock));
+    }
+
+    private void verifyMessageNotHandled() {
+        verify(messageHandlerInvokeAdapterMock, never()).execute(any(), any(), any());
+    }
 }
\ No newline at end of file
diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java
index ef41babb..6c689a38 100644
--- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java
@@ -1,6 +1,7 @@
 package io.github.tcdl.msb.collector;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -8,6 +9,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import io.github.tcdl.msb.ChannelManager;
+import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.support.TestUtils;
 
@@ -17,6 +19,8 @@
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 
+import java.util.Optional;
+
 @RunWith(MockitoJUnitRunner.class)
 public class CollectorManagerTest {
 
@@ -39,9 +43,9 @@ public void testHandleMessageRegisteredCollectorForTopic() {
         when(collectorMock.getRequestMessage()).thenReturn(originalAndReceivedMessage);
         CollectorManager collectorManager = new CollectorManager(TOPIC, channelManagerMock);
         collectorManager.registerCollector(collectorMock);
-        collectorManager.handleMessage(originalAndReceivedMessage, null);
+        Optional resolved = collectorManager.resolveMessageHandler(originalAndReceivedMessage);
 
-        verify(collectorMock).handleMessage(originalAndReceivedMessage, null);
+        assertEquals(resolved.get(), collectorMock);
     }
 
     @Test
@@ -49,9 +53,9 @@ public void testHandleMessageRegisteredCollectorForTopicUnexpectedCorrelationId(
         Message receivedMessage = TestUtils.createSimpleRequestMessage(TOPIC);
         CollectorManager collectorManager = new CollectorManager(TOPIC, channelManagerMock);
         collectorManager.registerCollector(collectorMock);
-        collectorManager.handleMessage(receivedMessage, null);
+        Optional resolved = collectorManager.resolveMessageHandler(receivedMessage);
 
-        verify(collectorMock, never()).handleMessage(receivedMessage, null);
+        assertFalse(resolved.isPresent());
     }
 
     @Test
@@ -61,9 +65,9 @@ public void testHandleMessageUnregisteredProperCollectorForTopic() {
 
         CollectorManager collectorManager = new CollectorManager("some-other-topic", channelManagerMock);
         collectorManager.registerCollector(collectorMock);
-        collectorManager.handleMessage(receivedMessage, null);
+        Optional resolved = collectorManager.resolveMessageHandler(receivedMessage);
 
-        verify(collectorMock, never()).handleMessage(receivedMessage, null);
+        assertFalse(resolved.isPresent());
     }
 
     @Test
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImplTest.java
new file mode 100644
index 00000000..a1888c7a
--- /dev/null
+++ b/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImplTest.java
@@ -0,0 +1,33 @@
+package io.github.tcdl.msb.impl;
+
+import io.github.tcdl.msb.MessageHandler;
+import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.api.message.Message;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SimpleMessageHandlerInvokeAdapterImplTest {
+
+    @Mock
+    MessageHandler messageHandler;
+
+    @Mock
+    AcknowledgementHandlerInternal acknowledgeHandler;
+
+    Message message;
+
+    @InjectMocks
+    SimpleMessageHandlerInvokeAdapterImpl adapter;
+
+    @Test
+    public void testDirectInvoke() {
+        adapter.execute(messageHandler, message, acknowledgeHandler);
+        verify(messageHandler, times(1)).handleMessage(message, acknowledgeHandler);
+    }
+
+}
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImplTest.java
new file mode 100644
index 00000000..5d1364c0
--- /dev/null
+++ b/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImplTest.java
@@ -0,0 +1,35 @@
+package io.github.tcdl.msb.impl;
+
+import io.github.tcdl.msb.MessageHandler;
+import io.github.tcdl.msb.api.message.Message;
+import io.github.tcdl.msb.support.TestUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SimpleMessageHandlerResolverImplTest {
+
+    @Mock
+    MessageHandler messageHandler;
+
+    Message message;
+
+    SimpleMessageHandlerResolverImpl resolver;
+
+    @Before
+    public void setUp() {
+        message = TestUtils.createSimpleResponseMessage("any");
+        resolver = new SimpleMessageHandlerResolverImpl(messageHandler);
+    }
+
+    @Test
+    public void testMessageHandlerResolutionByAnyMessage() {
+        assertEquals(messageHandler, resolver.resolveMessageHandler(message).get());
+    }
+
+}
diff --git a/pom.xml b/pom.xml
index d66b883d..e2fa2062 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,12 @@
                 msb-java-core
                 ${project.version}
             
+            
+                io.github.tcdl.msb
+                msb-java-core
+                ${project.version}
+                test-jar
+            
             
                 io.github.tcdl.msb
                 msb-java-amqp

From db6fb033303589788766aaf8e95615601e9aa7c5 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Thu, 17 Dec 2015 10:54:26 +0200
Subject: [PATCH 028/226] WEB-16678 time-consuming response callbacks no longer
 block incoming responses from being processed.

---
 .../msb/adapters/amqp/AmqpAdapterFactory.java |  6 +--
 ... => AmqpMessageHandlerInvokeStrategy.java} | 10 ++--
 .../adapters/amqp/AmqpAdapterFactoryTest.java |  6 +--
 ...AmqpMessageHandlerInvokeStrategyTest.java} |  4 +-
 .../io/github/tcdl/msb/ChannelManager.java    | 10 +++-
 .../java/io/github/tcdl/msb/Consumer.java     | 14 +++---
 .../tcdl/msb/adapters/AdapterFactory.java     |  6 +--
 ...java => MessageHandlerInvokeStrategy.java} |  7 ++-
 .../mock/MockAcknowledgementHandler.java      | 37 +++++++++++++++
 .../tcdl/msb/adapters/mock/MockAdapter.java   |  2 +-
 .../msb/adapters/mock/MockAdapterFactory.java |  9 ++--
 ...mpleMessageHandlerInvokeStrategyImpl.java} |  7 +--
 .../java/io/github/tcdl/msb/ConsumerTest.java | 46 +++++++++----------
 ...MessageHandlerInvokeStrategyImplTest.java} |  5 +-
 14 files changed, 110 insertions(+), 59 deletions(-)
 rename amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/{AmqpMessageHandlerInvokeAdapter.java => AmqpMessageHandlerInvokeStrategy.java} (73%)
 rename amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/{AmqpMessageHandlerInvokeAdapterTest.java => AmqpMessageHandlerInvokeStrategyTest.java} (93%)
 rename core/src/main/java/io/github/tcdl/msb/adapters/{MessageHandlerInvokeAdapter.java => MessageHandlerInvokeStrategy.java} (66%)
 create mode 100644 core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAcknowledgementHandler.java
 rename core/src/main/java/io/github/tcdl/msb/impl/{SimpleMessageHandlerInvokeAdapterImpl.java => SimpleMessageHandlerInvokeStrategyImpl.java} (58%)
 rename core/src/test/java/io/github/tcdl/msb/impl/{SimpleMessageHandlerInvokeAdapterImplTest.java => SimpleMessageHandlerInvokeStrategyImplTest.java} (82%)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
index bdeafcd9..d9c2e340 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
@@ -7,7 +7,7 @@
 import com.typesafe.config.ConfigFactory;
 import io.github.tcdl.msb.adapters.AdapterFactory;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
-import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
@@ -175,8 +175,8 @@ protected ExecutorService createConsumerThreadPool(AmqpBrokerConfig amqpBrokerCo
     }
 
     @Override
-    public MessageHandlerInvokeAdapter createMessageHandlerInvokeAdapter(String topic) {
-        return new AmqpMessageHandlerInvokeAdapter(consumerThreadPool);
+    public MessageHandlerInvokeStrategy createMessageHandlerInvokeStrategy(String topic) {
+        return new AmqpMessageHandlerInvokeStrategy(consumerThreadPool);
     }
 
     AmqpBrokerConfig getAmqpBrokerConfig() {
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java
similarity index 73%
rename from amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapter.java
rename to amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java
index 4ea53ebb..428569b6 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java
@@ -2,7 +2,7 @@
 
 import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
-import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy;
 import io.github.tcdl.msb.api.message.Message;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -10,19 +10,19 @@
 import java.util.concurrent.ExecutorService;
 
 /**
- * {@link MessageHandlerInvokeAdapter} implementation that preforms a an {@link MessageHandler} invocation
+ * {@link MessageHandlerInvokeStrategy} implementation that preforms a an {@link MessageHandler} invocation
  * in a separate thread.
  */
-public class AmqpMessageHandlerInvokeAdapter implements MessageHandlerInvokeAdapter {
+public class AmqpMessageHandlerInvokeStrategy implements MessageHandlerInvokeStrategy {
 
-    private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageHandlerInvokeAdapter.class);
+    private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageHandlerInvokeStrategy.class);
 
     private final ExecutorService consumerThreadPool;
 
     /**
      * @param consumerThreadPool thread pool to be used for {@link MessageHandler} invocations.
      */
-    public AmqpMessageHandlerInvokeAdapter(ExecutorService consumerThreadPool) {
+    public AmqpMessageHandlerInvokeStrategy(ExecutorService consumerThreadPool) {
         this.consumerThreadPool = consumerThreadPool;
     }
 
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
index b9eae19b..0a6f4fe2 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
@@ -9,7 +9,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
-import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
@@ -155,8 +155,8 @@ public void testInitGroupIdWithServiceName() {
 
     @Test
     public void testCreateMessageHandlerInvokeAdapter() {
-        MessageHandlerInvokeAdapter adapter = amqpAdapterFactory.createMessageHandlerInvokeAdapter("any");
-        assertTrue(adapter instanceof AmqpMessageHandlerInvokeAdapter);
+        MessageHandlerInvokeStrategy adapter = amqpAdapterFactory.createMessageHandlerInvokeStrategy("any");
+        assertTrue(adapter instanceof AmqpMessageHandlerInvokeStrategy);
     }
 
     @Test
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategyTest.java
similarity index 93%
rename from amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapterTest.java
rename to amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategyTest.java
index bc3fb93f..f9de0c8d 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategyTest.java
@@ -17,7 +17,7 @@
 import static org.mockito.Mockito.*;
 
 @RunWith(MockitoJUnitRunner.class)
-public class AmqpMessageHandlerInvokeAdapterTest {
+public class AmqpMessageHandlerInvokeStrategyTest {
     @Mock
     ExecutorService mockExecutor;
 
@@ -30,7 +30,7 @@ public class AmqpMessageHandlerInvokeAdapterTest {
     Message message = TestUtils.createMsbRequestMessage("any","any");
 
     @InjectMocks
-    AmqpMessageHandlerInvokeAdapter adapter;
+    AmqpMessageHandlerInvokeStrategy adapter;
 
     @Test
     public void testMessageHandling() {
diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
index 675fae95..bb5ef03e 100644
--- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
@@ -71,6 +71,14 @@ public synchronized boolean subscribe(String topic, MessageHandler messageHandle
         return subscribe(topic, new SimpleMessageHandlerResolverImpl(messageHandler));
     }
 
+    /**
+     * Start consuming messages on specified topic with handler resolver.
+     * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages.
+     *
+     * @param topic
+     * @param messageHandlerResolver resolver of {@link MessageHandler}  for processing messages
+     * @throws ConsumerSubscriptionException if subscriber for topic already exist
+     */
     public synchronized boolean subscribe(String topic, MessageHandlerResolver messageHandlerResolver) {
         Validate.notNull(topic, "field 'topic' is null");
         Validate.notNull(messageHandlerResolver, "field 'messageHandlerResolver' is null");
@@ -110,7 +118,7 @@ private Consumer createConsumer(String topic, MessageHandlerResolver messageHand
         Utils.validateTopic(topic);
 
         ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic);
-        MessageHandlerInvokeAdapter invokeAdapter = getAdapterFactory().createMessageHandlerInvokeAdapter(topic);
+        MessageHandlerInvokeStrategy invokeAdapter = getAdapterFactory().createMessageHandlerInvokeStrategy(topic);
         return new Consumer(adapter, invokeAdapter, topic, messageHandlerResolver, msbConfig, clock, channelMonitorAgent, validator, messageMapper);
     }
 
diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java
index 0b84d9b9..451e4f04 100644
--- a/core/src/main/java/io/github/tcdl/msb/Consumer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java
@@ -2,7 +2,7 @@
 
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
-import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.MetaMessage;
 import io.github.tcdl.msb.config.MsbConfig;
@@ -29,7 +29,7 @@ public class Consumer {
     private static final Logger LOG = LoggerFactory.getLogger(Consumer.class);
 
     private final ConsumerAdapter rawAdapter;
-    private final MessageHandlerInvokeAdapter messageHandlerInvokeAdapter;
+    private final MessageHandlerInvokeStrategy messageHandlerInvokeStrategy;
     private final String topic;
     private MsbConfig msbConfig;
     private ChannelMonitorAgent channelMonitorAgent;
@@ -48,12 +48,13 @@ public class Consumer {
      * @param validator validates incoming messages
      * @param messageMapper message deserializer
      */
-    public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvokeAdapter messageHandlerInvokeAdapter,
+    public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvokeStrategy messageHandlerInvokeStrategy,
             String topic, MessageHandlerResolver messageHandlerResolver, MsbConfig msbConfig,
             Clock clock, ChannelMonitorAgent channelMonitorAgent, JsonValidator validator, ObjectMapper messageMapper) {
 
         LOG.debug("Creating consumer for topic: {}", topic);
         Validate.notNull(rawAdapter, "the 'rawAdapter' must not be null");
+        Validate.notNull(messageHandlerInvokeStrategy, "the 'messageHandlerInvokeStrategy' must not be null");
         Validate.notNull(topic, "the 'topic' must not be null");
         Validate.notNull(messageHandlerResolver, "the 'messageHandlerResolver' must not be null");
         Validate.notNull(msbConfig, "the 'msbConfig' must not be null");
@@ -63,7 +64,7 @@ public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvokeAdapter messageH
         Validate.notNull(messageMapper, "the 'messageMapper' must not be null");
 
         this.rawAdapter = rawAdapter;
-        this.messageHandlerInvokeAdapter = messageHandlerInvokeAdapter;
+        this.messageHandlerInvokeStrategy = messageHandlerInvokeStrategy;
         this.topic = topic;
         this.messageHandlerResolver = messageHandlerResolver;
         this.msbConfig = msbConfig;
@@ -84,7 +85,8 @@ public void end() {
     }
 
     /**
-     * Process incoming message.
+     * Process raw incoming message JSON. If Message JSON is invalid or the message has been expired, the message
+     * will be rejected by means of {@link AcknowledgementHandlerInternal}.
      *
      * @param jsonMessage message to process
      */
@@ -111,7 +113,7 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern
         try {
             Optional messageHandler = messageHandlerResolver.resolveMessageHandler(message);
             if(messageHandler.isPresent()) {
-                messageHandlerInvokeAdapter.execute(messageHandler.get(), message, acknowledgeHandler);
+                messageHandlerInvokeStrategy.execute(messageHandler.get(), message, acknowledgeHandler);
             } else {
                 LOG.warn("Cant't resolve message handler for a message: {}", jsonMessage);
                 acknowledgeHandler.autoReject();
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
index 571705bc..0c370afb 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
@@ -33,11 +33,11 @@ public interface AdapterFactory {
     ConsumerAdapter createConsumerAdapter(String topic);
 
     /**
-     * Create {@link MessageHandlerInvokeAdapter} instance.
+     * Create {@link MessageHandlerInvokeStrategy} instance.
      * @param topic topic name
-     * @return {@link MessageHandlerInvokeAdapter} instance associated with a topic.
+     * @return {@link MessageHandlerInvokeStrategy} instance associated with a topic.
      */
-    MessageHandlerInvokeAdapter createMessageHandlerInvokeAdapter(String topic);
+    MessageHandlerInvokeStrategy createMessageHandlerInvokeStrategy(String topic);
 
     /**
      * Closes all resources used by amqp producers and consumers. Should be called for graceful shutdown.
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeStrategy.java
similarity index 66%
rename from core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeAdapter.java
rename to core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeStrategy.java
index fb9bd426..f398a472 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeAdapter.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeStrategy.java
@@ -7,9 +7,12 @@
 /**
  * Interface that defines a way to invoke {@link MessageHandler} to process a {@link Message} received.
  */
-public interface MessageHandlerInvokeAdapter {
+public interface MessageHandlerInvokeStrategy {
     /**
-     * Handle an incoming {@link Message} using {@link MessageHandler} provided.
+     * Handle an incoming {@link Message} using {@link MessageHandler} provided. After an invocation attempt, one of
+     * {@link AcknowledgementHandlerInternal} methods should be invoked (depending on result -
+     * {@link AcknowledgementHandlerInternal#autoConfirm()} should be used the processing was successful):
+     * it is required to call in order to confirm the message.
      * @param messageHandler {@link MessageHandler} instance related to a {@link Message} to be processed.
      * @param message  {@link Message} to be processed.
      * @param acknowledgeHandler acknowledgement handler.
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAcknowledgementHandler.java b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAcknowledgementHandler.java
new file mode 100644
index 00000000..eea8e57f
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAcknowledgementHandler.java
@@ -0,0 +1,37 @@
+package io.github.tcdl.msb.adapters.mock;
+
+import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+
+public class MockAcknowledgementHandler implements AcknowledgementHandlerInternal {
+    @Override public void autoConfirm() {
+
+    }
+
+    @Override public void autoReject() {
+
+    }
+
+    @Override public void autoRetry() {
+
+    }
+
+    @Override public void setAutoAcknowledgement(boolean autoAcknowledgement) {
+
+    }
+
+    @Override public boolean isAutoAcknowledgement() {
+        return true;
+    }
+
+    @Override public void confirmMessage() {
+
+    }
+
+    @Override public void retryMessage() {
+
+    }
+
+    @Override public void rejectMessage() {
+
+    }
+}
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java
index a4350a60..8c79ef33 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java
@@ -85,7 +85,7 @@ public void subscribe(RawMessageHandler messageHandler) {
 
                         if (messageHandler != null && jsonMessage != null) {
                             LOG.debug("Process message for topic {} [{}]", topic, jsonMessage);
-                            messageHandler.onMessage(jsonMessage, null);
+                            messageHandler.onMessage(jsonMessage, new MockAcknowledgementHandler());
                         } else {
                             try {
                                 Thread.sleep(CONSUMING_INTERVAL);
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java
index b0cc317a..090382f7 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java
@@ -3,11 +3,10 @@
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutorService;
-
-import io.github.tcdl.msb.impl.SimpleMessageHandlerInvokeAdapterImpl;
+import io.github.tcdl.msb.impl.SimpleMessageHandlerInvokeStrategyImpl;
 import io.github.tcdl.msb.adapters.AdapterFactory;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
-import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.support.Utils;
@@ -36,8 +35,8 @@ public ConsumerAdapter createConsumerAdapter(String topic) {
     }
 
     @Override
-    public MessageHandlerInvokeAdapter createMessageHandlerInvokeAdapter(String topic) {
-        return new SimpleMessageHandlerInvokeAdapterImpl();
+    public MessageHandlerInvokeStrategy createMessageHandlerInvokeStrategy(String topic) {
+        return new SimpleMessageHandlerInvokeStrategyImpl();
     }
 
     @Override
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImpl.java
similarity index 58%
rename from core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImpl.java
rename to core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImpl.java
index 87cd1c98..ca5887f2 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImpl.java
@@ -2,16 +2,17 @@
 
 import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
-import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy;
 import io.github.tcdl.msb.api.message.Message;
 
 /**
- * Trivial {@link MessageHandlerInvokeAdapter} implementation that preforms a direct {@link MessageHandler} invocation
+ * Trivial {@link MessageHandlerInvokeStrategy} implementation that preforms a direct {@link MessageHandler} invocation
  * to process a {@link Message} received.
  */
-public class SimpleMessageHandlerInvokeAdapterImpl implements MessageHandlerInvokeAdapter {
+public class SimpleMessageHandlerInvokeStrategyImpl implements MessageHandlerInvokeStrategy {
     @Override
     public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) {
         messageHandler.handleMessage(message, acknowledgeHandler);
+        acknowledgeHandler.autoConfirm();
     }
 }
diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
index f7667c7a..713c1de2 100644
--- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
@@ -5,7 +5,7 @@
 
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
-import io.github.tcdl.msb.adapters.MessageHandlerInvokeAdapter;
+import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy;
 import io.github.tcdl.msb.api.exception.JsonConversionException;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.MetaMessage;
@@ -51,7 +51,7 @@ public class ConsumerTest {
     private MessageHandlerResolver messageHandlerResolverMock;
 
     @Mock
-    private MessageHandlerInvokeAdapter messageHandlerInvokeAdapterMock;
+    private MessageHandlerInvokeStrategy messageHandlerInvokeStrategyMock;
 
     @Mock
     private AcknowledgementHandlerInternal acknowledgementHandlerMock;
@@ -70,47 +70,47 @@ public void setUp() {
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullAdapter() {
-        new Consumer(null, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(null, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullTopic() {
-        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, null, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeStrategyMock, null, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullMessageHandler() {
-        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, null, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, null, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullMsbConf() {
-        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, null, clock, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, null, clock, channelMonitorAgentMock, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullClock() {
-        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, null, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, null, channelMonitorAgentMock, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullMonitorAgent() {
-        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, null, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, null, validator, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullValidator() {
-        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, null, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, null, messageMapper);
     }
 
     @Test(expected = NullPointerException.class)
     public void testCreateConsumerNullMessageMapper() {
-        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, null);
+        new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, null);
     }
 
     @Test
     public void testSubscribeAdapterSubscribed() {
-        new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         verify(adapterMock).subscribe(any(ConsumerAdapter.RawMessageHandler.class));
     }
@@ -118,7 +118,7 @@ public void testSubscribeAdapterSubscribed() {
     @Test
     public void testValidMessageProcessedBySubscriber() throws JsonConversionException {
         Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
-        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
 
@@ -131,7 +131,7 @@ public void testMessageHandlerCantBeResolved() throws JsonConversionException {
                 .thenReturn(Optional.empty());
 
         Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
-        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
 
@@ -141,10 +141,10 @@ public void testMessageHandlerCantBeResolved() throws JsonConversionException {
 
     @Test
     public void testMessageHandlerInvokeException() throws JsonConversionException {
-        doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokeAdapterMock).execute(any(), any(), any());
+        doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokeStrategyMock).execute(any(), any(), any());
 
         Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
-        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
 
@@ -153,7 +153,7 @@ public void testMessageHandlerInvokeException() throws JsonConversionException {
 
     @Test
     public void testExceptionWhileMessageConvertingProcessedBySubscriber() throws JsonConversionException {
-        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock);
 
@@ -167,7 +167,7 @@ public void testHandleRawMessageConsumeFromTopicSkipValidation() {
         // disable validation
         when(msbConf.isValidateMessage()).thenReturn(false);
 
-        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         // create a message with required empty namespace
         Message message = TestUtils.createSimpleRequestMessage("");
@@ -182,7 +182,7 @@ public void testHandleRawMessageConsumeFromTopicSkipValidation() {
     @Test
     public void testHandleRawMessageConsumeFromTopic() throws JsonConversionException {
         Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
-        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
         verifyMessageHandled();
@@ -191,7 +191,7 @@ public void testHandleRawMessageConsumeFromTopic() throws JsonConversionExceptio
     @Test
     public void testHandleRawMessageConsumeFromTopicValidateThrowException() {
         MsbConfig msbConf = TestUtils.createMsbConfigurations();
-        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock);
         verifyMessageNotHandled();
@@ -201,7 +201,7 @@ public void testHandleRawMessageConsumeFromTopicValidateThrowException() {
     public void testHandleRawMessageConsumeFromServiceTopicValidateThrowException() {
         String service_topic = "_service:topic";
         MsbConfig msbConf = TestUtils.createMsbConfigurations();
-        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, service_topic, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, service_topic, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock);
         verifyMessageNotHandled();
@@ -210,7 +210,7 @@ public void testHandleRawMessageConsumeFromServiceTopicValidateThrowException()
     @Test
     public void testHandleRawMessageConsumeFromTopicExpiredMessage() throws JsonConversionException {
         Message expiredMessage = createExpiredMsbRequestMessageWithTopicTo(TOPIC);
-        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeAdapterMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
 
         consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper), acknowledgementHandlerMock);
         verifyMessageNotHandled();
@@ -229,10 +229,10 @@ private  Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) {
     }
 
     private void verifyMessageHandled() {
-        verify(messageHandlerInvokeAdapterMock, times(1)).execute(eq(messageHandlerMock), any(Message.class), eq(acknowledgementHandlerMock));
+        verify(messageHandlerInvokeStrategyMock, times(1)).execute(eq(messageHandlerMock), any(Message.class), eq(acknowledgementHandlerMock));
     }
 
     private void verifyMessageNotHandled() {
-        verify(messageHandlerInvokeAdapterMock, never()).execute(any(), any(), any());
+        verify(messageHandlerInvokeStrategyMock, never()).execute(any(), any(), any());
     }
 }
\ No newline at end of file
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImplTest.java
similarity index 82%
rename from core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImplTest.java
rename to core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImplTest.java
index a1888c7a..354a1b6b 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeAdapterImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImplTest.java
@@ -11,7 +11,7 @@
 import static org.mockito.Mockito.*;
 
 @RunWith(MockitoJUnitRunner.class)
-public class SimpleMessageHandlerInvokeAdapterImplTest {
+public class SimpleMessageHandlerInvokeStrategyImplTest {
 
     @Mock
     MessageHandler messageHandler;
@@ -22,12 +22,13 @@ public class SimpleMessageHandlerInvokeAdapterImplTest {
     Message message;
 
     @InjectMocks
-    SimpleMessageHandlerInvokeAdapterImpl adapter;
+    SimpleMessageHandlerInvokeStrategyImpl adapter;
 
     @Test
     public void testDirectInvoke() {
         adapter.execute(messageHandler, message, acknowledgeHandler);
         verify(messageHandler, times(1)).handleMessage(message, acknowledgeHandler);
+        verify(acknowledgeHandler, times(1)).autoConfirm();
     }
 
 }

From 711d987c988fce20b3ace1b90c29fa55584af18c Mon Sep 17 00:00:00 2001
From: rkatsyuryna 
Date: Thu, 17 Dec 2015 16:24:52 +0200
Subject: [PATCH 029/226] Fix issue: durationMS is 0 or negative value only

---
 .../tcdl/msb/api/message/MetaMessage.java     |  2 +-
 .../tcdl/msb/api/message/MetaMessageTest.java | 23 +++++++++++++++++++
 2 files changed, 24 insertions(+), 1 deletion(-)
 create mode 100644 core/src/test/java/io/github/tcdl/msb/api/message/MetaMessageTest.java

diff --git a/core/src/main/java/io/github/tcdl/msb/api/message/MetaMessage.java b/core/src/main/java/io/github/tcdl/msb/api/message/MetaMessage.java
index 9a86e0f0..13da5ed7 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/message/MetaMessage.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/message/MetaMessage.java
@@ -45,7 +45,7 @@ public Builder(Integer ttl, Instant createdAt, ServiceDetails serviceDetails, Cl
 
         public MetaMessage build() {
             publishedAt = clock.instant();
-            Long durationMs = Duration.between(publishedAt, this.createdAt).toMillis();;
+            Long durationMs = Duration.between(this.createdAt, publishedAt).toMillis();;
             return new MetaMessage(ttl, createdAt, publishedAt, durationMs, serviceDetails);
         }
     }
diff --git a/core/src/test/java/io/github/tcdl/msb/api/message/MetaMessageTest.java b/core/src/test/java/io/github/tcdl/msb/api/message/MetaMessageTest.java
new file mode 100644
index 00000000..5c6f4fa5
--- /dev/null
+++ b/core/src/test/java/io/github/tcdl/msb/api/message/MetaMessageTest.java
@@ -0,0 +1,23 @@
+package io.github.tcdl.msb.api.message;
+
+import static org.junit.Assert.assertTrue;
+import java.time.Clock;
+
+import com.typesafe.config.ConfigFactory;
+import io.github.tcdl.msb.config.MsbConfig;
+import org.junit.Test;
+
+/**
+ * Created by ruslan on 17.12.15.
+ */
+public class MetaMessageTest {
+
+    private Clock clock = Clock.systemDefaultZone();
+
+    @Test
+    public void testDurationIsPositivValue() {
+        MsbConfig msbConf = new MsbConfig(ConfigFactory.load());
+        MetaMessage metaMessage = new MetaMessage.Builder(0, clock.instant().minusMillis(1), msbConf.getServiceDetails(), clock).build();
+        assertTrue(metaMessage.getDurationMs() > 0);
+    }
+}

From 4d76ef5806177e5da64f05ff43ad424c52d8d2f1 Mon Sep 17 00:00:00 2001
From: rkatsyuryna 
Date: Fri, 18 Dec 2015 17:25:39 +0200
Subject: [PATCH 030/226] Make all reposne queues not durable and auto-delete

---
 .../msb/adapters/amqp/AmqpAdapterFactory.java |  4 +-
 .../adapters/amqp/AmqpConsumerAdapter.java    | 23 ++++--
 .../adapters/amqp/AmqpAdapterFactoryTest.java |  4 +-
 .../amqp/AmqpConsumerAdapterTest.java         | 80 ++++++++++++++++---
 .../tcdl/msb/cli/CliMessageSubscriber.java    |  2 +-
 .../msb/cli/CliMessageSubscriberTest.java     |  4 +-
 .../io/github/tcdl/msb/ChannelManager.java    | 28 ++++---
 .../tcdl/msb/adapters/AdapterFactory.java     |  3 +-
 .../msb/adapters/mock/MockAdapterFactory.java |  2 +-
 .../tcdl/msb/collector/CollectorManager.java  |  2 +-
 .../msb/ChannelManagerConcurrentTest.java     |  2 +-
 .../adapters/mock/MockAdapterFactoryTest.java |  4 +-
 .../msb/collector/CollectorManagerTest.java   |  8 +-
 13 files changed, 123 insertions(+), 43 deletions(-)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
index d9c2e340..dd017cb1 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
@@ -74,8 +74,8 @@ public ProducerAdapter createProducerAdapter(String topic) {
     }
 
     @Override
-    public ConsumerAdapter createConsumerAdapter(String topic) {
-        return new AmqpConsumerAdapter(topic, amqpBrokerConfig, connectionManager);
+    public ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
+        return new AmqpConsumerAdapter(topic, amqpBrokerConfig, connectionManager, isResponseTopic);
     }
 
     protected ConnectionFactory createConnectionFactory(AmqpBrokerConfig adapterConfig) {
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
index 945daa32..3a575340 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
@@ -1,17 +1,14 @@
 package io.github.tcdl.msb.adapters.amqp;
 
+import java.io.IOException;
+
+import com.rabbitmq.client.Channel;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import io.github.tcdl.msb.support.Utils;
-
-import java.io.IOException;
-import java.util.concurrent.ExecutorService;
-
 import org.apache.commons.lang3.Validate;
 
-import com.rabbitmq.client.Channel;
-
 public class AmqpConsumerAdapter implements ConsumerAdapter {
 
     private String topic;
@@ -19,18 +16,20 @@ public class AmqpConsumerAdapter implements ConsumerAdapter {
     private String exchangeName;
     private String consumerTag;
     private AmqpBrokerConfig adapterConfig;
+    private boolean isResponseTopic = false;
 
     /**
      * The constructor.
      * @param topic - a topic name associated with the adapter
      * @throws ChannelException if some problems during setup channel from RabbitMQ connection were occurred
      */
-    public AmqpConsumerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
+    public AmqpConsumerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager, boolean isResponseTopic) {
         Validate.notNull(topic, "the 'topic' must not be null");
 
         this.topic = topic;
         this.exchangeName = topic;
         this.adapterConfig = amqpBrokerConfig;
+        this.isResponseTopic = isResponseTopic;
 
         try {
             channel = connectionManager.obtainConnection().createChannel();
@@ -46,7 +45,7 @@ public AmqpConsumerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, Amqp
     @Override
     public void subscribe(RawMessageHandler msgHandler) {
         String groupId = adapterConfig.getGroupId().orElse(Utils.generateId());
-        boolean durable = adapterConfig.isDurable();
+        boolean durable = isDurable();
         int prefetchCount = adapterConfig.getPrefetchCount();
 
         String queueName = generateQueueName(topic, groupId, durable);
@@ -62,6 +61,14 @@ public void subscribe(RawMessageHandler msgHandler) {
         }
     }
 
+    protected boolean isDurable() {
+        if(isResponseTopic) {
+            //response topic is always auto-delete and not durable
+            return false;
+        }
+        return adapterConfig.isDurable();
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
index 0a6f4fe2..536c6f07 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
@@ -98,8 +98,8 @@ public ProducerAdapter createProducerAdapter(String topic) {
             }
 
             @Override
-            public ConsumerAdapter createConsumerAdapter(String topic) {
-                return new AmqpConsumerAdapter(topic, amqpConfig, mockConnectionManager);
+            public ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
+                return new AmqpConsumerAdapter(topic, amqpConfig, mockConnectionManager, isResponseTopic);
             }
 
             @Override
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
index 3a6d35d7..044db397 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
@@ -1,6 +1,7 @@
 package io.github.tcdl.msb.adapters.amqp;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyString;
@@ -43,7 +44,7 @@ public void setUp() throws Exception {
     @Test
     public void testTopicExchangeCreated() throws Exception {
         String topicName = "myTopic";
-        AmqpConsumerAdapter adapter = createAdapter(topicName, "myGroupId", false);
+        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf(topicName, "myGroupId", false);
 
         adapter.subscribe((jsonMessage, ackHandler) -> {
         });
@@ -55,12 +56,12 @@ public void testTopicExchangeCreated() throws Exception {
     public void testInitializationError() throws IOException {
         when(mockChannel.exchangeDeclare(anyString(), anyString(), anyBoolean(), anyBoolean(), any())).thenThrow(new IOException());
 
-        createAdapter("myTopic", "myGroupId", false);
+        createAdapterWithNonDurableConf("myTopic", "myGroupId", false);
     }
 
     @Test
     public void testSubscribeTransientQueueCreated() throws IOException {
-        AmqpConsumerAdapter adapter = createAdapter("myTopic", "myGroupId", false);
+        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", false);
         
         adapter.subscribe((jsonMessage, ackHandler) -> {
         });
@@ -75,9 +76,26 @@ public void testSubscribeTransientQueueCreated() throws IOException {
         verify(mockChannel).queueBind("myTopic.myGroupId.t", "myTopic", "");
     }
 
+    @Test
+    public void testSubscribeTransientQueueCreatedWhenIsResponseTopic() throws IOException {
+        AmqpConsumerAdapter adapter = createAdapterWithDurableConf("myTopic", "myGroupId", true);
+
+        adapter.subscribe((jsonMessage, ackHandler) -> {
+        });
+
+        // Verify that the queue has been declared with correct name and settings
+        verify(mockChannel).queueDeclare("myTopic.myGroupId.t", /* queue name */
+                false, /* durable */
+                false, /* exclusive */
+                true,  /* auto-delete */
+                null);
+        // Verify that the queue has been bound to the exchange
+        verify(mockChannel).queueBind("myTopic.myGroupId.t", "myTopic", "");
+    }
+
     @Test
     public void testSubscribeDurableQueueCreated() throws IOException {
-        AmqpConsumerAdapter adapter = createAdapter("myTopic", "myGroupId", true);
+        AmqpConsumerAdapter adapter = createAdapterWithDurableConf("myTopic", "myGroupId", false);
 
         adapter.subscribe((jsonMessage, ackHandler) -> {
         });
@@ -94,7 +112,7 @@ public void testSubscribeDurableQueueCreated() throws IOException {
 
     @Test
     public void testRegisteredHandlerInvoked() throws IOException {
-        AmqpConsumerAdapter adapter = createAdapter("myTopic", "myGroupId", false);
+        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", false);
         ConsumerAdapter.RawMessageHandler mockHandler = mock(ConsumerAdapter.RawMessageHandler.class);
 
         adapter.subscribe(mockHandler);
@@ -110,7 +128,7 @@ public void testRegisteredHandlerInvoked() throws IOException {
 
     @Test
     public void testUnsubscribe() throws IOException {
-        AmqpConsumerAdapter adapter = createAdapter("myTopic", "myGroupId", false);
+        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", false);
         String consumerTag = "my consumer tag";
         when(mockChannel.basicConsume(anyString(), anyBoolean(), any(Consumer.class))).thenReturn(consumerTag);
 
@@ -121,9 +139,53 @@ public void testUnsubscribe() throws IOException {
         verify(mockChannel).basicCancel(consumerTag);
     }
 
-    private AmqpConsumerAdapter createAdapter(String topic, String groupId, boolean durable) {
+    @Test
+    public void testIsDurableFalseIfResponseTopicAndNonDurableConfig() throws IOException {
+        boolean isResponseTopic = true;
+
+        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", isResponseTopic);
+
+        assertTrue(adapter.isDurable() == false);
+    }
+
+    @Test
+    public void testIsDurableFalseIfNotResponseTopicAndNonDurableConfig() throws IOException {
+        boolean isResponseTopic = false;
+
+        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", isResponseTopic);
+
+        assertTrue(adapter.isDurable() == false);
+    }
+
+    @Test
+    public void testIsDurableFalseIfResponseTopicAndDurableConfig() throws IOException {
+        boolean isResponseTopic = true;
+
+        AmqpConsumerAdapter adapter = createAdapterWithDurableConf("myTopic", "myGroupId", isResponseTopic);
+
+        assertTrue(adapter.isDurable() == false);
+    }
+
+    @Test
+    public void testIsDurableTrueIfNotResponseTopicAndDurableConfig() throws IOException {
+        boolean isResponseTopic = false;
+
+        AmqpConsumerAdapter adapter = createAdapterWithDurableConf("myTopic", "myGroupId", isResponseTopic);
+
+        assertTrue(adapter.isDurable() == true);
+    }
+
+    private AmqpConsumerAdapter createAdapterWithNonDurableConf(String topic, String groupId, boolean isResponseTopic) {
+        boolean isDurableConf = false;
+        AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
+                false, Optional.of(groupId), isDurableConf, 5, 20, 1, 5000, 1);
+        return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
+    }
+
+    private AmqpConsumerAdapter createAdapterWithDurableConf(String topic, String groupId, boolean isResponseTopic) {
+        boolean isDurableConf = true;
         AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
-                false, Optional.of(groupId), durable, 5, 20, 1, 5000, 1);
-        return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager);
+                false, Optional.of(groupId), isDurableConf, 5, 20, 1, 5000, 1);
+        return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
     }
 }
\ No newline at end of file
diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java
index 4a8c5d81..78604207 100644
--- a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java
+++ b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java
@@ -23,7 +23,7 @@ public CliMessageSubscriber(AdapterFactory adapterFactory) {
     public void subscribe(String topicName, CliMessageHandler handler) {
         synchronized (registeredTopics) {
             if (!registeredTopics.contains(topicName)) {
-                ConsumerAdapter adapter = adapterFactory.createConsumerAdapter(topicName);
+                ConsumerAdapter adapter = adapterFactory.createConsumerAdapter(topicName, false);
                 adapter.subscribe(handler);
                 registeredTopics.add(topicName);
             }
diff --git a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java
index ff52936f..d49375c2 100644
--- a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java
+++ b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java
@@ -24,7 +24,7 @@ public class CliMessageSubscriberTest {
     public void setUp() {
         mockAdapterFactory = mock(AdapterFactory.class);
         mockConsumerAdapter = mock(ConsumerAdapter.class);
-        when(mockAdapterFactory.createConsumerAdapter(TOPIC_NAME)).thenReturn(mockConsumerAdapter);
+        when(mockAdapterFactory.createConsumerAdapter(TOPIC_NAME, false)).thenReturn(mockConsumerAdapter);
 
         mockMessageHandler = mock(CliMessageHandler.class);
 
@@ -52,7 +52,7 @@ private void testInitialSubscription(String topicName) {
         // method under test
         subscriptionManager.subscribe(topicName, mockMessageHandler);
 
-        verify(mockAdapterFactory).createConsumerAdapter(topicName);
+        verify(mockAdapterFactory).createConsumerAdapter(topicName, false);
         verify(mockConsumerAdapter).subscribe(mockMessageHandler);
     }
 }
\ No newline at end of file
diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
index bb5ef03e..6a491757 100644
--- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
@@ -8,6 +8,7 @@
 import io.github.tcdl.msb.adapters.*;
 import io.github.tcdl.msb.api.Callback;
 import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException;
+import io.github.tcdl.msb.collector.CollectorManager;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.impl.SimpleMessageHandlerResolverImpl;
@@ -68,24 +69,33 @@ public Producer findOrCreateProducer(final String topic) {
      * @throws ConsumerSubscriptionException if subscriber for topic already exist
      */
     public synchronized boolean subscribe(String topic, MessageHandler messageHandler) {
-        return subscribe(topic, new SimpleMessageHandlerResolverImpl(messageHandler));
+        Validate.notNull(topic, "field 'topic' is null");
+        Validate.notNull(messageHandler, "field 'messageHandler' is null");
+        if (consumersByTopic.get(topic) != null) {
+            throw new ConsumerSubscriptionException("Subscriber for this topic: " + topic + " already exist");
+        } else {
+            Consumer newConsumer = createConsumer(topic, false, new SimpleMessageHandlerResolverImpl(messageHandler));
+            channelMonitorAgent.consumerTopicCreated(topic);
+            consumersByTopic.put(topic, newConsumer);
+            return false;
+        }
     }
 
     /**
-     * Start consuming messages on specified topic with handler resolver.
+     * Start consuming response messages on specified topic and pass processing to CollectorManager.
      * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages.
      *
      * @param topic
-     * @param messageHandlerResolver resolver of {@link MessageHandler}  for processing messages
+     * @param collectorManager resolver of {@link MessageHandler}  for processing messages
      * @throws ConsumerSubscriptionException if subscriber for topic already exist
      */
-    public synchronized boolean subscribe(String topic, MessageHandlerResolver messageHandlerResolver) {
+    public synchronized boolean subscribeForResponses(String topic, CollectorManager collectorManager) {
         Validate.notNull(topic, "field 'topic' is null");
-        Validate.notNull(messageHandlerResolver, "field 'messageHandlerResolver' is null");
+        Validate.notNull(collectorManager, "field 'collectorManager' is null");
         if (consumersByTopic.get(topic) != null) {
             throw new ConsumerSubscriptionException("Subscriber for this topic: " + topic + " already exist");
         } else {
-            Consumer newConsumer = createConsumer(topic, messageHandlerResolver);
+            Consumer newConsumer = createConsumer(topic, true, collectorManager);
             channelMonitorAgent.consumerTopicCreated(topic);
             consumersByTopic.put(topic, newConsumer);
             return false;
@@ -114,10 +124,10 @@ private Producer createProducer(String topic) {
         return new Producer(adapter, topic, handler, messageMapper);
     }
 
-    private Consumer createConsumer(String topic, MessageHandlerResolver messageHandlerResolver) {
+    private Consumer createConsumer(String topic, boolean isResponseTopic, MessageHandlerResolver messageHandlerResolver) {
         Utils.validateTopic(topic);
 
-        ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic);
+        ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic, isResponseTopic );
         MessageHandlerInvokeStrategy invokeAdapter = getAdapterFactory().createMessageHandlerInvokeStrategy(topic);
         return new Consumer(adapter, invokeAdapter, topic, messageHandlerResolver, msbConfig, clock, channelMonitorAgent, validator, messageMapper);
     }
@@ -128,7 +138,7 @@ public void shutdown() {
         LOG.info("Shutdown complete");
     }
 
-    public AdapterFactory getAdapterFactory() {
+    private AdapterFactory getAdapterFactory() {
         return this.adapterFactory;
     }
 
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
index 0c370afb..eac6f91a 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
@@ -27,10 +27,11 @@ public interface AdapterFactory {
 
     /**
      * @param topic topic name
+     * @param isResponseTopic specify if this topic used to handle response
      * @return Consumer Adapter associated with a topic
      * @throws ChannelException if some problems during creation were occurred
      */
-    ConsumerAdapter createConsumerAdapter(String topic);
+    ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic);
 
     /**
      * Create {@link MessageHandlerInvokeStrategy} instance.
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java
index 090382f7..78f0662f 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java
@@ -30,7 +30,7 @@ public ProducerAdapter createProducerAdapter(String topic) {
     }
 
     @Override
-    public ConsumerAdapter createConsumerAdapter(String topic) {
+    public ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
         return new MockAdapter(topic, consumerExecutors);
     }
 
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java
index 03cadba4..bc48a0c0 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java
@@ -55,7 +55,7 @@ public void registerCollector(Collector collector) {
 
         synchronized (this) {
             if (!isSubscribed) {
-                channelManager.subscribe(topic, this);
+                channelManager.subscribeForResponses(topic, this);
                 isSubscribed = true;
             }
         }
diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java
index 261b0387..1e19fab5 100644
--- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java
@@ -58,7 +58,7 @@ public void testConsumerUnsubscribeMultithreadInteraction() {
         String topic = "topic:test-remove-consumer-multithreaded";
 
         CollectorManager collectorManager = new CollectorManager(topic, channelManager);
-        channelManager.subscribe(topic, collectorManager);
+        channelManager.subscribeForResponses(topic, collectorManager);
 
         new MultithreadingTester().add(() -> {
             channelManager.unsubscribe(topic);
diff --git a/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactoryTest.java b/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactoryTest.java
index 3ba24d87..6f240453 100644
--- a/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactoryTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactoryTest.java
@@ -18,7 +18,7 @@ public class MockAdapterFactoryTest {
     @Test
     public void testCreateConsumerAdapter() {
         MockAdapterFactory mockAdapterFactory = new MockAdapterFactory();
-        ConsumerAdapter consumer = mockAdapterFactory.createConsumerAdapter("");
+        ConsumerAdapter consumer = mockAdapterFactory.createConsumerAdapter("", true);
         assertThat(consumer, instanceOf(MockAdapter.class));
         assertTrue(mockAdapterFactory.consumerExecutors.size() == 1);
     }
@@ -34,7 +34,7 @@ public void testCreateProducerAdapter() {
     @Test
     public void testShutdown() {
         MockAdapterFactory mockAdapterFactory = new MockAdapterFactory();
-        mockAdapterFactory.createConsumerAdapter("");
+        mockAdapterFactory.createConsumerAdapter("", true);
         assertTrue(mockAdapterFactory.consumerExecutors.size() == 1);
         mockAdapterFactory.shutdown();
         assertTrue(mockAdapterFactory.consumerExecutors.size() == 0);
diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java
index 6c689a38..3fe45fe6 100644
--- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java
@@ -76,7 +76,7 @@ public void testRegisterCollector() {
         collectorManager.registerCollector(collectorMock);
 
         assertEquals(1, collectorManager.collectorsByCorrelationId.size());
-        verify(channelManagerMock, times(1)).subscribe(TOPIC, collectorManager);
+        verify(channelManagerMock, times(1)).subscribeForResponses(TOPIC, collectorManager);
     }
 
     @Test
@@ -89,7 +89,7 @@ public void testRegisterMultipleCollectors() {
         collectorManager.registerCollector(secondCollectorMock);
 
         assertEquals(2, collectorManager.collectorsByCorrelationId.size());
-        verify(channelManagerMock, times(1)).subscribe(TOPIC, collectorManager);
+        verify(channelManagerMock, times(1)).subscribeForResponses(TOPIC, collectorManager);
     }
 
     @Test
@@ -97,7 +97,7 @@ public void testRegisterTheSameCollectorMultiplyTimes() {
         CollectorManager collectorManager = new CollectorManager(TOPIC, channelManagerMock);
         collectorManager.registerCollector(collectorMock);
 
-        verify(channelManagerMock, times(1)).subscribe(TOPIC, collectorManager);
+        verify(channelManagerMock, times(1)).subscribeForResponses(TOPIC, collectorManager);
 
         reset(channelManagerMock);
         collectorManager.registerCollector(collectorMock);
@@ -157,7 +157,7 @@ public void testRegisterCollectorAfterUnregisterLast() {
 
         reset(channelManagerMock);
         collectorManager.registerCollector(collectorMock);
-        verify(channelManagerMock, times(1)).subscribe(TOPIC, collectorManager);
+        verify(channelManagerMock, times(1)).subscribeForResponses(TOPIC, collectorManager);
     }
 
 }

From 4545149bad337f798754b70cc0ee6d639d69854a Mon Sep 17 00:00:00 2001
From: anha1 
Date: Fri, 18 Dec 2015 09:52:12 +0200
Subject: [PATCH 031/226] WEB-16849  - fix "onEnd" callback invocation before
 all response callbacks were invoked;  - handle redelivered response messages
 so they are not handled as separate responses;  - catch and
 "onEnd"/"onResponse" callbacks exceptions so they are not propagated leading
 to "autoRetry"

---
 .../tcdl/msb/acceptance/MsbTestHelper.java    |  19 +-
 .../bdd/steps/RequesterResponderSteps.java    |  12 +-
 .../java/io/github/tcdl/msb/Consumer.java     |  16 +-
 .../MessageHandlerInvokeStrategy.java         |   5 +
 .../github/tcdl/msb/collector/Collector.java  | 206 +++++++---
 .../ConsumedMessagesAwareMessageHandler.java  |  23 ++
 .../java/io/github/tcdl/msb/ConsumerTest.java |  33 ++
 .../github/tcdl/msb/api/ChannelMonitorIT.java |   9 +-
 .../tcdl/msb/collector/CollectorTest.java     | 375 ++++++++++++++++--
 9 files changed, 606 insertions(+), 92 deletions(-)
 create mode 100644 core/src/main/java/io/github/tcdl/msb/collector/ConsumedMessagesAwareMessageHandler.java

diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
index 3e373573..0e1606d4 100644
--- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
+++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
@@ -92,11 +92,21 @@ public  Requester createRequester(String contextName, String namespace, In
     }
 
     public  void sendRequest(Requester requester, Object payload, Integer waitForResponses, Callback responseCallback) throws Exception {
-        sendRequest(requester, payload, true, waitForResponses, null, responseCallback);
+        sendRequest(requester, payload, true, waitForResponses, null, responseCallback, null);
+    }
+
+    public  void sendRequest(Requester requester, Object payload, Integer waitForResponses, Callback responseCallback, Callback endCallback) throws Exception {
+        sendRequest(requester, payload, true, waitForResponses, null, responseCallback, endCallback);
     }
 
     public  void sendRequest(Requester requester, Object payload, boolean waitForAck, Integer waitForResponses,
             Callback ackCallback, Callback responseCallback) throws Exception {
+        sendRequest(requester, payload, waitForAck, waitForResponses,
+                 ackCallback, responseCallback, null);
+    }
+
+    public  void sendRequest(Requester requester, Object payload, boolean waitForAck, Integer waitForResponses,
+            Callback ackCallback, Callback responseCallback, Callback endCallback) throws Exception {
 
         requester.onAcknowledge((acknowledge, ackHandler) -> {
             System.out.println(">>> ACKNOWLEDGE: " + acknowledge);
@@ -111,6 +121,13 @@ public  void sendRequest(Requester requester, Object payload, boolean wait
             }
         });
 
+        requester.onEnd((end) -> {
+            System.out.println(">>> END: ");
+            if(endCallback != null) {
+                endCallback.call(null);
+            }
+        });
+
         requester.publish(payload);
     }
 
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index c4e6e8fb..e221b31c 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -174,21 +174,21 @@ public void sendRequest() throws Exception {
     public void sendRequest(String contextName) throws Exception {
         onBeforeRequest();
         RestPayload payload = helper.createFacetParserPayload("QUERY", null);
-        helper.sendRequest(requester, payload, responsesToExpectCount, this::onResponse);
+        helper.sendRequest(requester, payload, responsesToExpectCount, this::onResponse, this::onEnd);
     }
 
     @When("requester sends a request with query '$query'")
     public void sendRequestWithQuery(String query) throws Exception {
         onBeforeRequest();
         RestPayload payload = helper.createFacetParserPayload(query, null);
-        helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse);
+        helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse, this::onEnd);
     }
 
     @When("requester sends a request with body '$body'")
     public void sendRequestWithBody(String body) throws Exception {
         onBeforeRequest();
         RestPayload payload = helper.createFacetParserPayload(null, body);
-        helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse);
+        helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse, this::onEnd);
     }
 
     private void onBeforeRequest() {
@@ -213,6 +213,12 @@ private void onResponse(RestPayload>
         receivedResponseFuture.complete(payload.getBody());
     }
 
+    private void onEnd(Void in) {
+        if(responseCountDown != null && responseCountDown.getCount() > 0) {
+            Assert.fail("onEnd has been executed while not all responses were received yet, pending responses count: " + responseCountDown.getCount());
+        }
+    }
+
     @Then("requester gets response in $timeout ms")
     public void waitForResponse(long timeout) throws Exception {
         try {
diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java
index 451e4f04..da138ef2 100644
--- a/core/src/main/java/io/github/tcdl/msb/Consumer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java
@@ -5,6 +5,7 @@
 import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.MetaMessage;
+import io.github.tcdl.msb.collector.ConsumedMessagesAwareMessageHandler;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent;
 import io.github.tcdl.msb.support.JsonValidator;
@@ -110,10 +111,16 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern
             return;
         }
 
+        ConsumedMessagesAwareMessageHandler consumedMessagesAwareMessageHandler = null;
         try {
-            Optional messageHandler = messageHandlerResolver.resolveMessageHandler(message);
-            if(messageHandler.isPresent()) {
-                messageHandlerInvokeStrategy.execute(messageHandler.get(), message, acknowledgeHandler);
+            Optional optionalMessageHandler = messageHandlerResolver.resolveMessageHandler(message);
+            if(optionalMessageHandler.isPresent()) {
+                MessageHandler messageHandler = optionalMessageHandler.get();
+                if(messageHandler instanceof ConsumedMessagesAwareMessageHandler) {
+                    consumedMessagesAwareMessageHandler = ((ConsumedMessagesAwareMessageHandler) messageHandler);
+                    consumedMessagesAwareMessageHandler.notifyMessageConsumed();
+                }
+                messageHandlerInvokeStrategy.execute(messageHandler, message, acknowledgeHandler);
             } else {
                 LOG.warn("Cant't resolve message handler for a message: {}", jsonMessage);
                 acknowledgeHandler.autoReject();
@@ -121,6 +128,9 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern
         } catch (Exception e) {
             LOG.warn("Error while trying to handle a message: {}", jsonMessage, e);
             acknowledgeHandler.autoRetry();
+            if(consumedMessagesAwareMessageHandler != null) {
+                consumedMessagesAwareMessageHandler.notifyConsumedMessageIsLost();
+            }
         }
     }
 
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeStrategy.java b/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeStrategy.java
index f398a472..9ecbf7b9 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeStrategy.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeStrategy.java
@@ -2,6 +2,7 @@
 
 import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.api.AcknowledgementHandler;
 import io.github.tcdl.msb.api.message.Message;
 
 /**
@@ -13,9 +14,13 @@ public interface MessageHandlerInvokeStrategy {
      * {@link AcknowledgementHandlerInternal} methods should be invoked (depending on result -
      * {@link AcknowledgementHandlerInternal#autoConfirm()} should be used the processing was successful):
      * it is required to call in order to confirm the message.
+     * The method should always throw an exception when a message supplied can't be handled
+     * so there will be no {@link MessageHandler#handleMessage(Message, AcknowledgementHandler)} invocation.
+     *
      * @param messageHandler {@link MessageHandler} instance related to a {@link Message} to be processed.
      * @param message  {@link Message} to be processed.
      * @param acknowledgeHandler acknowledgement handler.
+     * @throws RuntimeException when a message can't be handled.
      */
     void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler);
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
index cd128379..7aae7f74 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
@@ -2,7 +2,7 @@
 
 import static io.github.tcdl.msb.support.Utils.ifNull;
 import static java.lang.Math.toIntExact;
-import io.github.tcdl.msb.MessageHandler;
+
 import io.github.tcdl.msb.api.AcknowledgementHandler;
 import io.github.tcdl.msb.api.Callback;
 import io.github.tcdl.msb.api.MessageContext;
@@ -17,12 +17,9 @@
 import java.time.Clock;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.atomic.LongAdder;
 import java.util.function.BiConsumer;
 
 import org.slf4j.Logger;
@@ -35,41 +32,69 @@
 /**
  * {@link Collector} is a component which collects responses and acknowledgements for sent requests.
  */
-public class Collector implements MessageHandler {
+public class Collector implements ConsumedMessagesAwareMessageHandler {
 
     private static final Logger LOG = LoggerFactory.getLogger(Collector.class);
 
-    private List ackMessages;
-    private List payloadMessages;
+    private final List ackMessages;
+    private final List payloadMessages;
 
-    private Map timeoutMsById;
-    private Map responsesRemainingById;
+    private final Map timeoutMsById;
+    private final Map responsesRemainingById;
+    private final Set handledMessagesIds;
 
-    private int timeoutMs;
-    private int currentTimeoutMs;
-    private Integer waitForAcksMs;
-    private Instant waitForAcksUntil;
+    private final int timeoutMs;
+    private volatile int currentTimeoutMs;
+    private final Integer waitForAcksMs;
+    private volatile Instant waitForAcksUntil;
 
-    private int responsesRemaining;
-    private boolean shouldWaitUntilResponseTimeout;
+    private volatile int responsesRemaining;
+    private final boolean shouldWaitUntilResponseTimeout;
 
-    private TypeReference payloadTypeReference;
+    private final TypeReference payloadTypeReference;
 
-    private long startedAt;
-    private TimeoutManager timeoutManager;
-    private ObjectMapper payloadMapper;
+    private final long startedAt;
+    private final TimeoutManager timeoutManager;
+    private final ObjectMapper payloadMapper;
 
-    private Clock clock;
-    private Message requestMessage;
+    private final Clock clock;
+    private final Message requestMessage;
 
-    private Optional> onRawResponse = Optional.empty();
-    private Optional> onResponse = Optional.empty();
-    private Optional> onAcknowledge = Optional.empty();
-    private Optional> onEnd = Optional.empty();
+    private final Optional> onRawResponse;
+    private final Optional> onResponse;
+    private final Optional> onAcknowledge;
+    private final Optional> onEnd;
 
     private ScheduledFuture ackTimeoutFuture;
     private ScheduledFuture responseTimeoutFuture;
-    private CollectorManager collectorManager;
+    private final CollectorManager collectorManager;
+
+    /**
+     * Count of consumed incoming messages so {@link #handleMessage} invocation is expected in future.
+     * Even redelivered messages increment this counter.
+     */
+    private final LongAdder consumedMessagesCount = new LongAdder();
+
+    /**
+     * Count of consumed incoming messages that were lost afterwards so {@link #handleMessage} invocation is
+     * no longer expected
+     */
+    private final LongAdder consumedAndLostMessagesCount = new LongAdder();
+
+    /**
+     * Counter of consumed incoming messages that are already handled by {@link #handleMessage}.
+     */
+    private final LongAdder handledMessagesHandledCount = new LongAdder();
+
+    /**
+     * Is the current instance unsubscribed from message source so new incoming messages are no longer expected.
+     */
+    private volatile boolean isUnsubscribed = false;
+
+    /**
+     * Was the "onEnd" callback invoked? Used to guarantee that "onEnd" will not be invoked more than once.
+     */
+    private volatile boolean isOnEndInvoked = false;
 
     public Collector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl msbContext, EventHandlers eventHandlers,
             TypeReference payloadTypeReference) {
@@ -85,6 +110,7 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt
         this.payloadMessages = new LinkedList<>();
         this.timeoutMsById = new HashMap<>();
         this.responsesRemainingById = new HashMap<>();
+        this.handledMessagesIds = new HashSet<>();
 
         this.waitForAcksMs = requestOptions.getAckTimeout();
         this.waitForAcksUntil = null;
@@ -96,9 +122,7 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt
 
         this.responsesRemaining = waitForResponses;
 
-        if (waitForResponses == RequestOptions.WAIT_FOR_RESPONSES_UNTIL_TIMEOUT) {
-            shouldWaitUntilResponseTimeout = true;
-        }
+        shouldWaitUntilResponseTimeout = (waitForResponses == RequestOptions.WAIT_FOR_RESPONSES_UNTIL_TIMEOUT);
 
         this.payloadTypeReference = payloadTypeReference;
 
@@ -107,9 +131,24 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt
             onResponse = Optional.ofNullable(eventHandlers.onResponse());
             onAcknowledge = Optional.ofNullable(eventHandlers.onAcknowledge());
             onEnd = Optional.ofNullable(eventHandlers.onEnd());
+        } else {
+            onRawResponse = Optional.empty();
+            onResponse = Optional.empty();
+            onAcknowledge = Optional.empty();
+            onEnd = Optional.empty();
         }
     }
 
+    @Override
+    public synchronized void notifyMessageConsumed() {
+        consumedMessagesCount.increment();
+    }
+
+    @Override
+    public synchronized void notifyConsumedMessageIsLost() {
+        consumedAndLostMessagesCount.increment();
+    }
+
     boolean isAwaitingAcks() {
         return this.waitForAcksUntil != null && waitForAcksUntil.isAfter(Instant.now());
     }
@@ -132,16 +171,21 @@ public void handleMessage(Message incomingMessage, AcknowledgementHandler acknow
 
         JsonNode rawPayload = incomingMessage.getRawPayload();
         MessageContext messageContext = createMessageContext(acknowledgeHandler, incomingMessage);
-        if (Utils.isPayloadPresent(rawPayload)) {
+        boolean isWithPayload = Utils.isPayloadPresent(rawPayload);
+
+        if (isWithPayload) {
             LOG.debug("[correlation ids: {}-{}] Received Payload {}",
                     requestMessage.getCorrelationId(), incomingMessage.getCorrelationId(), rawPayload);
             payloadMessages.add(incomingMessage);
-            onRawResponse.ifPresent(handler -> handler.accept(incomingMessage, messageContext));
-
-            T payload = Utils.convert(rawPayload, payloadTypeReference, payloadMapper);
-            onResponse.ifPresent(handler -> handler.accept(payload, messageContext));
-
-            incResponsesRemaining(-1);
+            try {
+                onRawResponse.ifPresent(handler -> handler.accept(incomingMessage, messageContext));
+
+                T payload = Utils.convert(rawPayload, payloadTypeReference, payloadMapper);
+                onResponse.ifPresent(handler -> handler.accept(payload, messageContext));
+            } catch (Exception e) {
+                //do not propagate exception outside of this method in order to prevent autoRetry for responses
+                LOG.warn("Unexpected exception during response handler invocation", e);
+            }
         } else {
             LOG.debug("[correlation ids: {}-{}] Received {}",
                     requestMessage.getCorrelationId(), incomingMessage.getCorrelationId(), incomingMessage.getAck());
@@ -151,31 +195,72 @@ public void handleMessage(Message incomingMessage, AcknowledgementHandler acknow
 
         processAck(incomingMessage.getAck());
 
-        if (isAwaitingResponses())
-            return;
-
-        //set ack timer task in case we received ALL expected responses but still have to wait for ack
-        if (isAwaitingAcks()) {
-            waitForAcks();
-            return;
+        synchronized (this) {
+            handledMessagesHandledCount.increment();
+            updateCounters(incomingMessage, isWithPayload);
+
+            boolean isInvokeOnEnd = false;
+            if (!isAwaitingResponses()) {
+                //set ack timer task in case we received ALL expected responses but still have to wait for ack
+                if (isAwaitingAcks()) {
+                    waitForAcks();
+                } else {
+                    isInvokeOnEnd = true;
+                }
+            }
+
+            isInvokeOnEnd = isInvokeOnEnd || isNoMoreMessagesHandlingPossible();
+
+            if(isInvokeOnEnd) {
+                LOG.debug("[correlation ids: {}] All messages has been received", requestMessage.getCorrelationId());
+                end();
+            }
         }
-
-        LOG.debug("[correlation ids: {}] All messages has been received", requestMessage.getCorrelationId());
-        end();
     }
 
     MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandler, Message originalMessage) {
         return new MessageContextImpl(acknowledgementHandler, originalMessage);
     }
-    
-    protected void end() {
-        LOG.debug("[correlation id: {}] Stop response processing ", requestMessage.getCorrelationId());
 
+    protected synchronized void end() {
+        LOG.debug("[correlation id: {}] Stop response processing ", requestMessage.getCorrelationId());
         cancelAckTimeoutTask();
         cancelResponseTimeoutTask();
 
         collectorManager.unregisterCollector(this);
-        onEnd.ifPresent(handler -> handler.call(null));
+        isUnsubscribed = true;
+
+        if(!isOnEndInvoked && isAllConsumedMessagesHandled()) {
+            isOnEndInvoked = true;
+            LOG.debug("[correlation id: {}] triggering 'onEnd' callback", requestMessage.getCorrelationId());
+            try {
+                onEnd.ifPresent(handler -> handler.call(null));
+            } catch (Exception e) {
+                LOG.warn("Unexpected exception during 'onEnd' handler invocation", e);
+            }
+        }
+    }
+
+    /**
+     * Returns true if no more {@link #handleMessage} invocations are expected.
+     */
+    private boolean isNoMoreMessagesHandlingPossible() {
+        return isUnsubscribed && isAllConsumedMessagesHandled();
+    }
+
+    /**
+     * Returns true if all incoming messages consumed at the moment were handled by {@link #handleMessage}.
+     * But it is possible, that some new messages will be consumed and handled afterwards.
+     */
+    private synchronized boolean isAllConsumedMessagesHandled() {
+        int consumed = consumedMessagesCount.intValue();
+        int handled = handledMessagesHandledCount.intValue();
+        int consumedAndLost = consumedAndLostMessagesCount.intValue();
+
+        LOG.debug("[correlation id: {}] Messages consumed: {}; handled: {} consumed and lost: {}  ",
+                requestMessage.getCorrelationId(), consumed, handled, consumedAndLost);
+
+        return (consumed == consumedAndLost + handled);
     }
 
     void processAck(Acknowledge acknowledge) {
@@ -225,7 +310,22 @@ private int getMaxTimeoutMs() {
         return maxTimeoutMs;
     }
 
-    private Integer incResponsesRemaining(Integer inc) {
+    private synchronized void updateCounters(Message message, boolean isWithPayload) {
+        String id = message.getId();
+
+        /**
+         * Don't update remaining messages counter when a message id was already recorder so the current
+         * message is a redelivery of a previous one.
+         */
+        if(!handledMessagesIds.contains(id)) {
+            if(isWithPayload) {
+                incResponsesRemaining(-1);
+            }
+            handledMessagesIds.add(message.getId());
+        }
+    }
+
+    private synchronized Integer incResponsesRemaining(Integer inc) {
         return (responsesRemaining = Math.max(responsesRemaining + inc, 0));
     }
 
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/ConsumedMessagesAwareMessageHandler.java b/core/src/main/java/io/github/tcdl/msb/collector/ConsumedMessagesAwareMessageHandler.java
new file mode 100644
index 00000000..ed49fb8b
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/collector/ConsumedMessagesAwareMessageHandler.java
@@ -0,0 +1,23 @@
+package io.github.tcdl.msb.collector;
+
+import io.github.tcdl.msb.MessageHandler;
+import io.github.tcdl.msb.api.AcknowledgementHandler;
+import io.github.tcdl.msb.api.message.Message;
+
+/**
+ * Interface used to notify {@link io.github.tcdl.msb.MessageHandler} regarding a count
+ * of expected  {@link io.github.tcdl.msb.MessageHandler#handleMessage(Message, AcknowledgementHandler)} invocations.
+ */
+public interface ConsumedMessagesAwareMessageHandler extends MessageHandler {
+    /**
+     * Should be invoked when an incoming message has been consumed so {@link io.github.tcdl.msb.MessageHandler#handleMessage(Message, AcknowledgementHandler)}
+     * will be invoked to process it in future.
+     */
+    void notifyMessageConsumed();
+
+    /**
+     * Should be invoked when an incoming message that was consumed previously has been lost so {@link io.github.tcdl.msb.MessageHandler#handleMessage(Message, AcknowledgementHandler)}
+     * invocation is no longer expected.
+     */
+    void notifyConsumedMessageIsLost();
+}
diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
index 713c1de2..c373e86e 100644
--- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
@@ -10,6 +10,7 @@
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.MetaMessage;
 import io.github.tcdl.msb.api.message.Topics;
+import io.github.tcdl.msb.collector.ConsumedMessagesAwareMessageHandler;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent;
 import io.github.tcdl.msb.support.JsonValidator;
@@ -47,9 +48,15 @@ public class ConsumerTest {
     @Mock
     private MessageHandler messageHandlerMock;
 
+    @Mock
+    private ConsumedMessagesAwareMessageHandler consumedMessagesAwareMessageHandlerMock;
+
     @Mock
     private MessageHandlerResolver messageHandlerResolverMock;
 
+    @Mock
+    private MessageHandlerResolver consumedMessagesAwareMessageHandlerResolverMock;
+
     @Mock
     private MessageHandlerInvokeStrategy messageHandlerInvokeStrategyMock;
 
@@ -66,6 +73,9 @@ public class ConsumerTest {
     public void setUp() {
         when(messageHandlerResolverMock.resolveMessageHandler(any()))
                 .thenReturn(Optional.of(messageHandlerMock));
+
+        when(consumedMessagesAwareMessageHandlerResolverMock.resolveMessageHandler(any()))
+                .thenReturn(Optional.of(consumedMessagesAwareMessageHandlerMock));
     }
 
     @Test(expected = NullPointerException.class)
@@ -125,6 +135,29 @@ public void testValidMessageProcessedBySubscriber() throws JsonConversionExcepti
         verifyMessageHandled();
     }
 
+    @Test
+    public void testConsumedMessagesAwareMessageHandlerNotifiedWhenMessageHandled() throws JsonConversionException {
+        Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, consumedMessagesAwareMessageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+
+        consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
+
+        verify(consumedMessagesAwareMessageHandlerMock, times(1)).notifyMessageConsumed();
+    }
+
+    @Test
+    public void testConsumedMessagesAwareMessageHandlerNotifiedWhenMessageLost() throws JsonConversionException {
+        doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokeStrategyMock).execute(any(), any(), any());
+
+        Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, consumedMessagesAwareMessageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper);
+
+        consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock);
+
+        verify(consumedMessagesAwareMessageHandlerMock, times(1)).notifyMessageConsumed();
+        verify(consumedMessagesAwareMessageHandlerMock, times(1)).notifyConsumedMessageIsLost();
+    }
+
     @Test
     public void testMessageHandlerCantBeResolved() throws JsonConversionException {
         when(messageHandlerResolverMock.resolveMessageHandler(any()))
diff --git a/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java b/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java
index 1449ab7b..28f443ff 100644
--- a/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java
+++ b/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java
@@ -146,16 +146,19 @@ public void testHeartbeatUnexpectedMessage() throws InterruptedException {
         //need to await for original request for heartbeat to be send to simulate response with same correlationId
         Message requestMessage = awaitHeartBeatRequestSent();
 
-        Message brokenResponseMessage = TestUtils.createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(),
+        Message brokenResponseMessage1 = TestUtils.createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(),
+                requestMessage.getCorrelationId(),
+                " unexpected statistics format received");
+        Message brokenResponseMessage2 = TestUtils.createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(),
                 requestMessage.getCorrelationId(),
                 " unexpected statistics format received");
         Message responseMessage = TestUtils
                 .createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(), requestMessage.getCorrelationId(),
                         payload);
         //simulate 3 heartbeatResponses: 1 valid and 2 broken
-        MockAdapter.pushRequestMessage(requestMessage.getTopics().getResponse(), Utils.toJson(brokenResponseMessage, msbContext.getPayloadMapper()));
+        MockAdapter.pushRequestMessage(requestMessage.getTopics().getResponse(), Utils.toJson(brokenResponseMessage1, msbContext.getPayloadMapper()));
         MockAdapter.pushRequestMessage(requestMessage.getTopics().getResponse(), Utils.toJson(responseMessage, msbContext.getPayloadMapper()));
-        MockAdapter.pushRequestMessage(requestMessage.getTopics().getResponse(), Utils.toJson(brokenResponseMessage, msbContext.getPayloadMapper()));
+        MockAdapter.pushRequestMessage(requestMessage.getTopics().getResponse(), Utils.toJson(brokenResponseMessage2, msbContext.getPayloadMapper()));
 
         assertTrue("Heartbeat response was not received",
                 heartBeatResponseReceived.await(HEARTBEAT_TIMEOUT_MS * 2, TimeUnit.MILLISECONDS));
diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java
index 8e1dc810..e0d4d1bc 100644
--- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java
@@ -1,25 +1,19 @@
 package io.github.tcdl.msb.collector;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.assertj.core.api.Assertions.fail;
+import static org.junit.Assert.*;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
+
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.api.AcknowledgementHandler;
 import io.github.tcdl.msb.api.Callback;
 import io.github.tcdl.msb.api.MessageContext;
 import io.github.tcdl.msb.api.RequestOptions;
-import io.github.tcdl.msb.api.exception.JsonConversionException;
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
@@ -32,9 +26,11 @@
 
 import java.io.IOException;
 import java.time.Clock;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.LongAdder;
 import java.util.function.BiConsumer;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -174,6 +170,7 @@ MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandle
         };
         
         // method under test
+        notifyMessagesConsumed(collector, 1);
         collector.handleMessage(responseMessage, acknowledgeHandler);
 
         RestPayload expectedPayload = new RestPayload.Builder()
@@ -187,7 +184,6 @@ MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandle
         assertFalse(collector.getAckMessages().contains(responseMessage));
     }
 
-    @Test(expected = JsonConversionException.class)
     public void testHandleResponseConversionFailed() {
         String bodyText = "some body";
         Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
@@ -214,12 +210,11 @@ MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandle
         };
 
         // make sure that onRawResponse is called even if conversion of payload to custom type fails
-        try {
-            collector.handleMessage(responseMessage, ackHandler);
-        } finally {
-            verify(onRawResponse).accept(responseMessage, messageContext);
-            verify(onResponse, never()).accept(any(), any());
-        }
+
+        collector.handleMessage(responseMessage, ackHandler);
+
+        verify(onRawResponse).accept(responseMessage, messageContext);
+        verify(onResponse, never()).accept(any(), any());
     }
 
     @Test
@@ -244,6 +239,7 @@ public void testHandleResponseEndEventNoResponsesRemaining() {
         when(eventHandlers.onEnd()).thenReturn(onEnd);
         Collector collector = createCollector();
 
+        notifyMessagesConsumed(collector, 1);
         AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class);
         collector.handleMessage(responseMessageWithAck, ackHandler);
 
@@ -271,6 +267,8 @@ public void testHandleResponseLastResponse() {
         when(eventHandlers.onEnd()).thenReturn(onEnd);
 
         Collector collector = createCollector();
+
+        notifyMessagesConsumed(collector, 1);
         AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class);
         collector.handleMessage(responseMessage, ackHandler);
 
@@ -287,8 +285,8 @@ public void testHandleResponseLastResponse() {
     @SuppressWarnings({ "rawtypes", "unchecked" })
     public void testHandleResponseWaitForOneMoreResponse() {
         String bodyText = "some body";
-        Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
-
+        Message responseMessage1 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
+        Message responseMessage2 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
         /*ackTimeout = 0, responseTimeout=200; waitForResponses = 2
         */
         int responseTimeout = 200;
@@ -309,13 +307,16 @@ public void testHandleResponseWaitForOneMoreResponse() {
                 .build();
 
         AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class);
+
         //send first response
-        collector.handleMessage(responseMessage, ackHandler);
+        notifyMessagesConsumed(collector, 1);
+        collector.handleMessage(responseMessage1, ackHandler);
         verify(onResponse).accept(expectedPayload, messageContextMock);
         verify(onEnd, never()).call(any());
 
         //send last response
-        collector.handleMessage(responseMessage, ackHandler);
+        notifyMessagesConsumed(collector, 1);
+        collector.handleMessage(responseMessage2, ackHandler);
         verify(onResponse, times(2)).accept(expectedPayload, messageContextMock);
         verify(timeoutManagerMock, never()).enableResponseTimeout(eq(responseTimeout), eq(collector));
         verify(timeoutManagerMock, never()).enableAckTimeout(eq(0), eq(collector));
@@ -404,6 +405,7 @@ public void testHandleResponseNoResponsesRemainingAndWaitUntilAckBeforeNow() {
 
         AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class);
         //send payload response
+        notifyMessagesConsumed(collector, 1);
         collector.handleMessage(responseMessage, ackHandler);
         verify(timeoutManagerMock, never()).enableResponseTimeout(eq(0), eq(collector));
         verify(timeoutManagerMock, never()).enableAckTimeout(eq(ackTimeoutMs), eq(collector));
@@ -429,6 +431,7 @@ public void testHandleResponseReceivedAckWithSameTimeoutValue() {
         Acknowledge ack = new Acknowledge.Builder().withResponderId(Utils.generateId()).withResponsesRemaining(0).withTimeoutMs(timeoutMs).build();
         Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId());
 
+        notifyMessagesConsumed(collector, 1);
         AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class);
         collector.handleMessage(responseMessageWithAck, ackHandler);
 
@@ -457,6 +460,7 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndNoResponsesRemaini
                 .build();
         Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId());
 
+        notifyMessagesConsumed(collector, 1);
         AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class);
         collector.handleMessage(responseMessageWithAck, ackHandler);
 
@@ -544,8 +548,44 @@ public void testHandleResponseReceivedAcksWithUpdatedTimeoutAndResponsesRemainin
     @SuppressWarnings({ "rawtypes", "unchecked" })
     public void testHandleResponseEnsureResponsesRemainingIsDecreased() {
         String bodyText = "some body";
-        Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
+        Message responseMessage1 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
+        Message responseMessage2 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
+        /*ackTimeout = 0, responseTimeout=200; waitForResponses = 2
+        */
+        int responseTimeout = 200;
+        int responsesRemaining = 2;
+        when(requestOptionsMock.getAckTimeout()).thenReturn(0);
+        when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout);
+        when(requestOptionsMock.getWaitForResponses()).thenReturn(responsesRemaining);
+
+        BiConsumer onResponse = mock(BiConsumer.class);
+        when(eventHandlers.onResponse()).thenReturn(onResponse);
+        Callback onEnd = mock(Callback.class);
+        when(eventHandlers.onEnd()).thenReturn(onEnd);
+
+        Collector collector = createCollector();
+        collector.listenForResponses();
+
+        assertEquals(responsesRemaining, collector.getResponsesRemaining());
 
+        notifyMessagesConsumed(collector, 2);
+        //send first response
+        collector.handleMessage(responseMessage1, null);
+        assertEquals(1, collector.getResponsesRemaining());
+        verify(onEnd, never()).call(any());
+
+        //send last response
+        collector.handleMessage(responseMessage2, null);
+        assertEquals(0, collector.getResponsesRemaining());
+        verify(onEnd).call(any());
+    }
+
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testHandleResponseRedelivery() {
+        String bodyText = "some body";
+        Message responseMessage1 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
+        Message responseMessage2 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
         /*ackTimeout = 0, responseTimeout=200; waitForResponses = 2
         */
         int responseTimeout = 200;
@@ -564,17 +604,96 @@ public void testHandleResponseEnsureResponsesRemainingIsDecreased() {
 
         assertEquals(responsesRemaining, collector.getResponsesRemaining());
 
+        notifyMessagesConsumed(collector, 2);
         //send first response
-        collector.handleMessage(responseMessage, null);
+        collector.handleMessage(responseMessage1, null);
         assertEquals(1, collector.getResponsesRemaining());
         verify(onEnd, never()).call(any());
 
+        //redeliver first response
+        collector.handleMessage(responseMessage1, null);
+        assertEquals(1, collector.getResponsesRemaining());
+        verify(onEnd, never()).call(any());
+
+        notifyMessagesConsumed(collector, 1);
         //send last response
-        collector.handleMessage(responseMessage, null);
+        collector.handleMessage(responseMessage2, null);
         assertEquals(0, collector.getResponsesRemaining());
         verify(onEnd).call(any());
     }
 
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testEndInvokedWhenOnResponseCallbacksThrowExceptions() {
+        String bodyText = "some body";
+        Message responseMessage1 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
+        Message responseMessage2 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
+        /*ackTimeout = 0, responseTimeout=200; waitForResponses = 2
+        */
+        int responseTimeout = 200;
+        int responsesRemaining = 2;
+        when(requestOptionsMock.getAckTimeout()).thenReturn(0);
+        when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout);
+        when(requestOptionsMock.getWaitForResponses()).thenReturn(responsesRemaining);
+
+        BiConsumer onResponse = mock(BiConsumer.class);
+        when(eventHandlers.onResponse()).thenReturn(onResponse);
+        doThrow(new RuntimeException("Unexpected error in a callback!")).when(onResponse).accept(any(), any());
+
+        Callback onEnd = mock(Callback.class);
+        when(eventHandlers.onEnd()).thenReturn(onEnd);
+
+        Collector collector = createCollector();
+        collector.listenForResponses();
+
+        assertEquals(responsesRemaining, collector.getResponsesRemaining());
+
+        notifyMessagesConsumed(collector, 2);
+
+        //send first response
+        collector.handleMessage(responseMessage1, null);
+        verify(onEnd, never()).call(any());
+
+        collector.handleMessage(responseMessage2, null);
+
+        verify(onEnd, times(1)).call(any());
+    }
+
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testEndThrowsExceptions() {
+        String bodyText = "some body";
+        Message responseMessage1 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
+
+        /*ackTimeout = 0, responseTimeout=200; waitForResponses = 2
+        */
+        int responseTimeout = 200;
+        int responsesRemaining = 1;
+        when(requestOptionsMock.getAckTimeout()).thenReturn(0);
+        when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout);
+        when(requestOptionsMock.getWaitForResponses()).thenReturn(responsesRemaining);
+
+        BiConsumer onResponse = mock(BiConsumer.class);
+        when(eventHandlers.onResponse()).thenReturn(onResponse);
+
+        Callback onEnd = mock(Callback.class);
+        when(eventHandlers.onEnd()).thenReturn(onEnd);
+
+        doThrow(new RuntimeException("Unexpected error in a callback!")).when(onEnd).call(any());
+
+        Collector collector = createCollector();
+        collector.listenForResponses();
+
+        assertEquals(responsesRemaining, collector.getResponsesRemaining());
+
+        notifyMessagesConsumed(collector, 1);
+
+        //send first response
+        collector.handleMessage(responseMessage1, null);
+        verify(onEnd, times(1)).call(any());
+    }
+
+
     @Test
     @SuppressWarnings({ "rawtypes", "unchecked" })
     public void testHandleResponseReceivedAckWithUpdatedTimeoutAndOneResponseRemaining() throws InterruptedException, IOException {
@@ -601,12 +720,14 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndOneResponseRemaini
         Collector collector = createCollector();
         collector.listenForResponses();
 
+        notifyMessagesConsumed(collector, 1);
         Acknowledge ack = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(1).withTimeoutMs(timeoutMsInAck)
                 .build();
         Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId());
         collector.handleMessage(responseMessageWithAck, null);
 
         //simulate responder response
+        notifyMessagesConsumed(collector, 1);
         Acknowledge responseAck = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(-1).build();
         ObjectMapper payloadMapper = TestUtils.createMessageMapper();
         JsonNode payloadNode = payloadMapper.readValue(String.format("{\"body\": \"%s\" }", "test response payload body"), JsonNode.class);
@@ -616,7 +737,187 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndOneResponseRemaini
         //send message
         collector.handleMessage(responderMessage, null);
 
-        verify(onEnd, timeout(1500)).call(any());
+        verify(onEnd, after(1500).times(1)).call(any());
+    }
+
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testTimeoutFiredWhileMessagesExpected() throws InterruptedException, IOException {
+        int timeoutMs = 300;
+        int timeoutMsInAck = 600;
+        String responderId = "b";
+
+        when(requestOptionsMock.getAckTimeout()).thenReturn(0);
+        when(requestOptionsMock.getResponseTimeout()).thenReturn(timeoutMs);
+        when(requestOptionsMock.getWaitForResponses()).thenReturn(0);
+
+        this.msbContext = TestUtils.createMsbContextBuilder()
+                .withMsbConfigurations(msbConfigurationsMock)
+                .withMessageFactory(messageFactoryMock)
+                .withChannelManager(channelManagerMock)
+                .withClock(Clock.systemDefaultZone())
+                .withTimeoutManager(new TimeoutManager(1))
+                .withCollectorManagerFactory(collectorManagerFactoryMock)
+                .build();
+
+        Callback onEnd = mock(Callback.class);
+        when(eventHandlers.onEnd()).thenReturn(onEnd);
+
+        @SuppressWarnings("unchecked")
+        BiConsumer onResponse = (restPayload, messageContext) -> {
+            try {
+                Thread.sleep(900);
+            } catch (InterruptedException e) {
+                fail("Interrupted");
+            }
+        };
+
+        when(eventHandlers.onResponse()).thenReturn(onResponse);
+
+        Collector collector = createCollector();
+        collector.listenForResponses();
+
+        Acknowledge ack = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(99).withTimeoutMs(timeoutMsInAck)
+                .build();
+        Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId());
+        notifyMessagesConsumed(collector, 1);
+        collector.handleMessage(responseMessageWithAck, null);
+
+        //simulate responder response
+        Acknowledge responseAck = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(-1).build();
+        ObjectMapper payloadMapper = TestUtils.createMessageMapper();
+        JsonNode payloadNode = payloadMapper.readValue(String.format("{\"body\": \"%s\" }", "test response payload body"), JsonNode.class);
+
+        Message responderMessage = TestUtils.createMsbResponseMessage(responseAck, payloadNode, TOPIC_RESPONSE, "someCorrelationId");
+
+        //send message
+        notifyMessagesConsumed(collector, 1);
+        collector.handleMessage(responderMessage, null);
+
+        verify(onEnd, after(1500).times(1)).call(any());
+    }
+
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testTimeoutFiredDuringMessageHandling() throws Exception {
+        int timeoutMs = 300;
+        int timeoutMsInAck = 600;
+        String responderId = "b";
+
+        when(requestOptionsMock.getAckTimeout()).thenReturn(0);
+        when(requestOptionsMock.getResponseTimeout()).thenReturn(timeoutMs);
+        when(requestOptionsMock.getWaitForResponses()).thenReturn(0);
+
+        this.msbContext = TestUtils.createMsbContextBuilder()
+                .withMsbConfigurations(msbConfigurationsMock)
+                .withMessageFactory(messageFactoryMock)
+                .withChannelManager(channelManagerMock)
+                .withClock(Clock.systemDefaultZone())
+                .withTimeoutManager(new TimeoutManager(1))
+                .withCollectorManagerFactory(collectorManagerFactoryMock)
+                .build();
+
+        CompletableFuture timeOnEnd = new CompletableFuture<>();
+        CompletableFuture  timeAfterHandler = new CompletableFuture<>();
+        LongAdder onEndCounter = new LongAdder();
+
+        Callback onEnd = (Void in) -> {
+            System.out.println("onEnd invoked");
+            onEndCounter.increment();
+            timeOnEnd.complete(System.currentTimeMillis());
+        };
+
+        when(eventHandlers.onEnd()).thenReturn(onEnd);
+
+        @SuppressWarnings("unchecked")
+        BiConsumer onResponse = (restPayload, messageContext) -> {
+            try {
+                System.out.println("slow onResponse start");
+                Thread.sleep(1500);
+                timeAfterHandler.complete(System.currentTimeMillis());
+                System.out.println("slow onResponse finish");
+            } catch (InterruptedException e) {
+                fail("Interrupted");
+            }
+        };
+
+        when(eventHandlers.onResponse()).thenReturn(onResponse);
+
+        Collector collector = createCollector();
+        collector.listenForResponses();
+
+        Acknowledge ack = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(1).withTimeoutMs(timeoutMsInAck)
+                .build();
+        Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId());
+        notifyMessagesConsumed(collector, 1);
+        collector.handleMessage(responseMessageWithAck, null);
+
+        //simulate responder response
+        Acknowledge responseAck = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(-1).build();
+        ObjectMapper payloadMapper = TestUtils.createMessageMapper();
+        JsonNode payloadNode = payloadMapper.readValue(String.format("{\"body\": \"%s\" }", "test response payload body"), JsonNode.class);
+
+        Message responderMessage = TestUtils.createMsbResponseMessage(responseAck, payloadNode, TOPIC_RESPONSE, "someCorrelationId");
+
+        //send message
+        notifyMessagesConsumed(collector, 1);
+        collector.handleMessage(responderMessage, null);
+        long timeAfterHandlerValue = timeAfterHandler.get(2000, TimeUnit.MILLISECONDS);
+        long timeOnEndValue = timeOnEnd.get(2000, TimeUnit.MILLISECONDS);
+
+        assertTrue("onEnd should not be invoked by timer: it should be invoked after the last message handling instead: "
+                + "handler complete:" + timeAfterHandlerValue + ", onEnd invoked:" + timeOnEndValue,
+                timeAfterHandlerValue <= timeOnEndValue);
+        assertEquals(1, onEndCounter.intValue());
+    }
+
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testHandleResponseReceivedAckWithUpdatedTimeoutAndOneResponseLost() throws InterruptedException, IOException {
+        int timeoutMs = 200;
+        int timeoutMsInAck = 500;
+        String responderId = "b";
+
+        when(requestOptionsMock.getAckTimeout()).thenReturn(0);
+        when(requestOptionsMock.getResponseTimeout()).thenReturn(timeoutMs);
+        when(requestOptionsMock.getWaitForResponses()).thenReturn(0);
+
+        this.msbContext = TestUtils.createMsbContextBuilder()
+                .withMsbConfigurations(msbConfigurationsMock)
+                .withMessageFactory(messageFactoryMock)
+                .withChannelManager(channelManagerMock)
+                .withClock(Clock.systemDefaultZone())
+                .withTimeoutManager(new TimeoutManager(1))
+                .withCollectorManagerFactory(collectorManagerFactoryMock)
+                .build();
+
+        Callback onEnd = mock(Callback.class);
+        when(eventHandlers.onEnd()).thenReturn(onEnd);
+
+        Collector collector = createCollector();
+        collector.listenForResponses();
+
+        Acknowledge ack = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(3).withTimeoutMs(timeoutMsInAck)
+                .build();
+        Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId());
+        notifyMessagesConsumed(collector, 1);
+        collector.handleMessage(responseMessageWithAck, null);
+
+        //simulate responder response
+        Acknowledge responseAck = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(-1).build();
+        ObjectMapper payloadMapper = TestUtils.createMessageMapper();
+        JsonNode payloadNode = payloadMapper.readValue(String.format("{\"body\": \"%s\" }", "test response payload body"), JsonNode.class);
+
+        Message responderMessage = TestUtils.createMsbResponseMessage(responseAck, payloadNode, TOPIC_RESPONSE, "someCorrelationId");
+        notifyMessagesConsumed(collector, 1);
+        notifyMessagesLost(collector, 1);
+        verify(onEnd, never()).call(any());
+
+        //send message
+        notifyMessagesConsumed(collector, 1);
+        collector.handleMessage(responderMessage, null);
+
+        verify(onEnd, after(1500).times(1)).call(any());
     }
 
     @Test
@@ -648,6 +949,7 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndTwoResponsesRemain
         Acknowledge ack = new Acknowledge.Builder().withResponderId(responderId).withResponsesRemaining(2).withTimeoutMs(timeoutMsInAck)
                 .build();
         Message responseMessageWithAck = TestUtils.createMsbResponseMessageWithAckNoPayload(ack, TOPIC_RESPONSE, originalMessage.getCorrelationId());
+        notifyMessagesConsumed(collector, 1);
         collector.handleMessage(responseMessageWithAck, null);
 
         //simulate responder response
@@ -655,14 +957,17 @@ public void testHandleResponseReceivedAckWithUpdatedTimeoutAndTwoResponsesRemain
         ObjectMapper payloadMapper = TestUtils.createMessageMapper();
         JsonNode payloadNode = payloadMapper.readValue(String.format("{\"body\": \"%s\" }", "test response payload body"), JsonNode.class);
 
-        Message responderMessage = TestUtils.createMsbResponseMessage(responseAck, payloadNode, TOPIC_RESPONSE,  "someCorrelationId");
+        Message responderMessage1 = TestUtils.createMsbResponseMessage(responseAck, payloadNode, TOPIC_RESPONSE,  "someCorrelationId");
+        Message responderMessage2 = TestUtils.createMsbResponseMessage(responseAck, payloadNode, TOPIC_RESPONSE,  "someCorrelationId");
+
+        notifyMessagesConsumed(collector, 2);
 
         //send first message
-        collector.handleMessage(responderMessage, null);
+        collector.handleMessage(responderMessage1, null);
 
         //send second message after initial response
         Thread.sleep(200);
-        collector.handleMessage(responderMessage, null);
+        collector.handleMessage(responderMessage2, null);
 
         verify(onEnd, timeout(1500)).call(any());
     }
@@ -807,6 +1112,18 @@ MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandle
            
         };
     }
+
+    private void notifyMessagesConsumed(Collector collector, int messagesCount) {
+        for(int i = 0; i < messagesCount; i++) {
+            ((ConsumedMessagesAwareMessageHandler)collector).notifyMessageConsumed();
+        }
+    }
+
+    private void notifyMessagesLost(Collector collector, int messagesCount) {
+        for(int i = 0; i < messagesCount; i++) {
+            ((ConsumedMessagesAwareMessageHandler)collector).notifyConsumedMessageIsLost();
+        }
+    }
     
 
 }
\ No newline at end of file

From 91b395b7f3dc3a27d750e7f14119bf1d8b283b19 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Sun, 20 Dec 2015 15:59:53 +0200
Subject: [PATCH 032/226] WEB-16849 invoke onEnd when last message expected has
 been lost

---
 .../github/tcdl/msb/collector/Collector.java  |  3 ++
 .../tcdl/msb/collector/CollectorTest.java     | 46 +++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
index 7aae7f74..fe410f67 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
@@ -147,6 +147,9 @@ public synchronized void notifyMessageConsumed() {
     @Override
     public synchronized void notifyConsumedMessageIsLost() {
         consumedAndLostMessagesCount.increment();
+        if(isNoMoreMessagesHandlingPossible()) {
+            end();
+        }
     }
 
     boolean isAwaitingAcks() {
diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java
index e0d4d1bc..7ec0b2c0 100644
--- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java
@@ -323,6 +323,52 @@ public void testHandleResponseWaitForOneMoreResponse() {
         verify(onEnd).call(any());
     }
 
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testHandleResponseLastResponseLostAfterTimeout() {
+        String bodyText = "some body";
+        Message responseMessage1 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
+        Message responseMessage2 = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
+        /*ackTimeout = 0, responseTimeout=200; waitForResponses = 2
+        */
+        int responseTimeout = 200;
+        when(requestOptionsMock.getAckTimeout()).thenReturn(0);
+        when(requestOptionsMock.getResponseTimeout()).thenReturn(responseTimeout);
+        when(requestOptionsMock.getWaitForResponses()).thenReturn(2);
+
+        BiConsumer onResponse = mock(BiConsumer.class);
+        when(eventHandlers.onResponse()).thenReturn(onResponse);
+        Callback onEnd = mock(Callback.class);
+        when(eventHandlers.onEnd()).thenReturn(onEnd);
+
+        Collector collector = createCollector();
+        collector.listenForResponses();
+
+        RestPayload expectedPayload = new RestPayload.Builder()
+                .withBody(bodyText)
+                .build();
+
+        AcknowledgementHandler ackHandler = mock(AcknowledgementHandler.class);
+
+        //send first response
+        notifyMessagesConsumed(collector, 3);
+        collector.handleMessage(responseMessage1, ackHandler);
+        verify(onResponse).accept(expectedPayload, messageContextMock);
+        verify(onEnd, never()).call(any());
+
+        //timeout
+        collector.end();
+        verify(onEnd, never()).call(any());
+
+        //second response lost
+        notifyMessagesLost(collector, 1);
+        verify(onEnd, never()).call(any());
+
+        //third response lost
+        notifyMessagesLost(collector, 1);
+        verify(onEnd).call(any());
+    }
+
     @Test
     @SuppressWarnings({ "rawtypes", "unchecked" })
     public void testHandleResponseNoResponsesRemainingButAwaitAck() {

From c50bb6e587d3d81f60b322c12c70e3246007c799 Mon Sep 17 00:00:00 2001
From: rkatsyuryna 
Date: Mon, 21 Dec 2015 12:36:01 +0200
Subject: [PATCH 033/226] update .md file with durable queues changes

---
 doc/MSB.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/doc/MSB.md b/doc/MSB.md
index 6626b09c..0282aab5 100644
--- a/doc/MSB.md
+++ b/doc/MSB.md
@@ -328,10 +328,10 @@ The section `brokerConfig` from [reference.conf](/core/src/main/resources/refere
 
 `groupId` – microservices with the same `groupId` subscribed to the same namespace will receive messages from that namespace in round-robin fashion. If microservices have different `groupId`s and subscribed to the same namespace then all of those microservices are going to receive a copy of a message from that namespace.
 
-`durable` – queue durability, true/false. Defaults to false.
-Specifies one of the two types of the queue:
-`durable` = `true` – durable queue, that survives broker restart
-`durable` = `false` – transient queue, is auto removed once the listening service is stopped.
+`durable` – queue durability, true/false. Opposite for this value is used to set auto-delete option. Defaults to false.
+Specifies types of the queue used for incomming messages (except for responses that are always non-durable and auto-delete):
+`durable` = `true` – durable queue, that survives broker restart. Auto-delete is set to false.
+`durable` = `false` – transient queue, not survives broker restart. Auto-delete is set to true.
 
 See for more [detail](https://www.rabbitmq.com/tutorials/amqp-concepts.html).
 

From 31bb174723895d011888129082cca38605383e53 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Mon, 21 Dec 2015 13:30:12 +0200
Subject: [PATCH 034/226] WEB-16004 documentation/config defaults updates

---
 amqp/src/main/resources/amqp.conf                  |  4 ++--
 .../java/io/github/tcdl/msb/api/Requester.java     |  1 +
 doc/MSB.md                                         |  4 ++--
 release-notes.html                                 | 14 ++++++++------
 4 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf
index 44825893..6768e3a4 100644
--- a/amqp/src/main/resources/amqp.conf
+++ b/amqp/src/main/resources/amqp.conf
@@ -17,7 +17,7 @@ config.amqp = {
   durable = false
   consumerThreadPoolSize = 5
   # -1 means unlimited
-  consumerThreadPoolQueueCapacity = 20
+  consumerThreadPoolQueueCapacity = -1
 
   # Interval of the heartbeats that are used to detect broken connections. Zero for none. See for more details: https://www.rabbitmq.com/heartbeats.html
   heartbeatIntervalSec = 1
@@ -25,5 +25,5 @@ config.amqp = {
   networkRecoveryIntervalMs = 5000
   
   # Specify the size of the limit of unacknowledged messages on a queue basis
-  prefetchCount = 1
+  prefetchCount = 10
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/api/Requester.java b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
index d5ba3e67..d64de8ea 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/Requester.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
@@ -89,6 +89,7 @@ public interface Requester {
     
     /**
      * Registers a callback to be called when all expected responses for request message are processes or awaiting timeout for responses occurred.
+     * Will be invoked only after all incoming responses will be processed.
      *
      * @param endHandler callback to be called
      * @return requester
diff --git a/doc/MSB.md b/doc/MSB.md
index de044c36..bbb24cad 100644
--- a/doc/MSB.md
+++ b/doc/MSB.md
@@ -337,7 +337,7 @@ See for more [detail](https://www.rabbitmq.com/tutorials/amqp-concepts.html).
 
 `consumerThreadPoolSize` – number of consumer threads used to process incoming messages. Defines the level of parallelism. Default is 5.
 
-`consumerThreadPoolQueueCapacity` – maximum number of requests waiting in FIFO queue to be processed by consumer thread pool. Should be positive integer or -1. Value of -1 stands for unlimited.
+`consumerThreadPoolQueueCapacity` – maximum number of requests waiting in FIFO queue to be processed by consumer thread pool. Should be positive integer or -1. Value of -1 stands for unlimited. The default value is -1.
 
 The following fields are optional in case of broker running on local machine
 but are mandatory when using broker on remote computer. When there is a need to override the default values these fields are specified in application.conf file as additional `brokerConfig` parameters.
@@ -353,7 +353,7 @@ More references on how to configure the broker to allow the remote access with t
 
 `heartbeatIntervalSec` - interval of the heartbeats that are used to detect broken connections. Zero for none. See for more details: https://www.rabbitmq.com/heartbeats.html. Defaults to 1 second.
 
-`prefetchCount` - Specify the limit number of unacknowledged messages on a channel when consuming. The default value is 10.
+`prefetchCount` - Specify the limit number of unacknowledged messages on a channel when consuming. Value of 0 stands for unlimited. The default value is 10.
 
 
 ## AMQP adapter
diff --git a/release-notes.html b/release-notes.html
index abe304f1..87a6ba7b 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -14,15 +14,17 @@ 

December 7, 2015

------------------------------------------------------------------------------------ Features of MSB-Java version 1.4.0: - - Added prefetch count for AMQP adapter + - Added prefetch count for AMQP adapter; (for details see https://www.rabbitmq.com/consumer-prefetch.html) - - Implemented explicit confirm/reject delivered messages + - Implemented explicit confirm/reject delivered messages; + - Fixed an issue when `onEnd` callback could be invoked by timeout while not all incoming responses are processed yet; + - Fixed an issue when time-consuming `onResponse` callbacks could block incoming responses processing. Features of MSB-Java version 1.3.0: - - Removed REST-style payload constraint on parameters type - - Updated Requester behavior. It waits for acks even if configured to - `waitForResponses: 0` - + - Removed REST-style payload constraint on parameters type; + - Updated Requester behavior. It waits for acks even if configured to + `waitForResponses: 0`. +
From 1b24d3f5154e717f749d49d955d54af37f362c22 Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 28 Dec 2015 12:32:50 +0200 Subject: [PATCH 035/226] WEB-16878 MsbSteps fix - shutdown issue when several instances of a single microservice were started --- .../msb/acceptance/bdd/steps/MsbSteps.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/MsbSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/MsbSteps.java index 5cec0442..cb7d7199 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/MsbSteps.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/MsbSteps.java @@ -13,32 +13,35 @@ public class MsbSteps { private final static String DEFAULT_PACKAGE = "io.github.tcdl.msb.examples."; - MsbTestHelper helper = MsbTestHelper.getInstance(); - private Map microserviceMap = new HashMap<>(); + protected final MsbTestHelper helper = MsbTestHelper.getInstance(); + private final Map microserviceMap = new HashMap<>(); @Given("microservice $microservice") - public void startMicroservice(String microservice) throws Throwable { - Class microserviceClass = getClass().getClassLoader().loadClass(resolveClass(microservice)); - Method startMethod = microserviceClass.getMethod("start", MsbContext.class); - startMethod.invoke(microserviceClass.newInstance(), helper.getDefaultContext()); + public synchronized void startMicroservice(String microservice) throws Throwable { + startMicroserviceInternal(microservice, helper.getDefaultContext()); } @Given("start microservice $microservice with context $contextName") - public void startMicroserviceWithContext(String microservice, String contextName) throws Throwable { + public synchronized void startMicroserviceWithContext(String microservice, String contextName) throws Throwable { + startMicroserviceInternal(microservice, helper.getContext(contextName)); + } + + private void startMicroserviceInternal(String microservice, MsbContext context) throws Exception { Class microserviceClass = getClass().getClassLoader().loadClass(resolveClass(microservice)); Method startMethod = microserviceClass.getMethod("start", MsbContext.class); Object microserviceInstance = microserviceClass.newInstance(); - startMethod.invoke(microserviceInstance, helper.getContext(contextName)); + startMethod.invoke(microserviceInstance, context); microserviceMap.putIfAbsent(microservice, microserviceInstance); } @Then("stop microservice $microservice") - public void stopMicroservice(String microservice) throws Throwable { + public synchronized void stopMicroservice(String microservice) throws Throwable { if (microserviceMap.containsKey(microservice)) { Object microserviceInstance = microserviceMap.get(microservice); Class microserviceClass = getClass().getClassLoader().loadClass(resolveClass(microservice)); Method stopMethod = microserviceClass.getMethod("stop"); stopMethod.invoke(microserviceInstance); + microserviceMap.remove(microservice); } } From ef0f711b4dd08d75472dd71ba4e3b8e6b3b33414 Mon Sep 17 00:00:00 2001 From: rkatsyuryna Date: Fri, 25 Dec 2015 12:52:03 +0200 Subject: [PATCH 036/226] Fix:channel monitor agent throws exceptions upon microservices shutdown --- .../src/test/resources/scenarios/channel_recreation.story | 4 ---- .../io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java | 2 +- .../io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java | 2 +- .../tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java | 2 +- .../tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java | 2 +- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/acceptance/src/test/resources/scenarios/channel_recreation.story b/acceptance/src/test/resources/scenarios/channel_recreation.story index 7fb25663..822f8c6e 100644 --- a/acceptance/src/test/resources/scenarios/channel_recreation.story +++ b/acceptance/src/test/resources/scenarios/channel_recreation.story @@ -18,10 +18,6 @@ And response equals |result| |hello jbehave| -When shutdown context contextResponder -And requester from contextRequester sends a request -Then log contains 'Shutdown is NOT initiated by application. Resetting the channel.' - When init MSB context contextResponder And responder server from contextResponder listens on namespace test:jbehave And responder server responds with '{"result": "hello jbehave"}' diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java index 3a575340..726627e8 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java @@ -33,7 +33,7 @@ public AmqpConsumerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, Amqp try { channel = connectionManager.obtainConnection().createChannel(); - channel.exchangeDeclare(exchangeName, "fanout", false /* durable */, true /* auto-delete */, null); + channel.exchangeDeclare(exchangeName, "fanout", false /* durable */, false /* auto-delete */, null); } catch (IOException e) { throw new ChannelException("Failed to setup channel from ActiveMQ connection", e); } diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java index 034474fa..007cd11a 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java @@ -27,7 +27,7 @@ public AmqpProducerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, Amqp this.amqpAutoRecoveringChannel = new AmqpAutoRecoveringChannel(connectionManager); try { - amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, "fanout", false /* durable */, true /* auto-delete */, null); + amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, "fanout", false /* durable */, false /* auto-delete */, null); } catch (IOException e) { throw new ChannelException("Failed to setup channel from ActiveMQ connection", e); } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java index 044db397..0a57ab8c 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java @@ -49,7 +49,7 @@ public void testTopicExchangeCreated() throws Exception { adapter.subscribe((jsonMessage, ackHandler) -> { }); - verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null); + verify(mockChannel).exchangeDeclare(topicName, "fanout", false, false, null); } @Test(expected = RuntimeException.class) diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java index 7fb18aae..70977980 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java @@ -44,7 +44,7 @@ public void testExchangeCreated() throws IOException { new AmqpProducerAdapter(topicName, mockAmqpBrokerConfig, mockAmqpConnectionManager); - verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null); + verify(mockChannel).exchangeDeclare(topicName, "fanout", false, false, null); } @Test(expected = RuntimeException.class) From d24085c975e6b0af5f8e22722e834f77d772feca Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 4 Jan 2016 16:50:14 +0200 Subject: [PATCH 037/226] Revert "Merge pull request #315 from tcdl/fix_exception_in_monitor" This reverts commit 31f501332f18d7d1a202c202ef07c443f2648c97, reversing changes made to fe9d7f6082fb7821c511dc2e18d7ab30e101bb02. --- .../src/test/resources/scenarios/channel_recreation.story | 4 ++++ .../io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java | 2 +- .../io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java | 2 +- .../tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java | 2 +- .../tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/acceptance/src/test/resources/scenarios/channel_recreation.story b/acceptance/src/test/resources/scenarios/channel_recreation.story index 822f8c6e..7fb25663 100644 --- a/acceptance/src/test/resources/scenarios/channel_recreation.story +++ b/acceptance/src/test/resources/scenarios/channel_recreation.story @@ -18,6 +18,10 @@ And response equals |result| |hello jbehave| +When shutdown context contextResponder +And requester from contextRequester sends a request +Then log contains 'Shutdown is NOT initiated by application. Resetting the channel.' + When init MSB context contextResponder And responder server from contextResponder listens on namespace test:jbehave And responder server responds with '{"result": "hello jbehave"}' diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java index 726627e8..3a575340 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java @@ -33,7 +33,7 @@ public AmqpConsumerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, Amqp try { channel = connectionManager.obtainConnection().createChannel(); - channel.exchangeDeclare(exchangeName, "fanout", false /* durable */, false /* auto-delete */, null); + channel.exchangeDeclare(exchangeName, "fanout", false /* durable */, true /* auto-delete */, null); } catch (IOException e) { throw new ChannelException("Failed to setup channel from ActiveMQ connection", e); } diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java index 007cd11a..034474fa 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java @@ -27,7 +27,7 @@ public AmqpProducerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, Amqp this.amqpAutoRecoveringChannel = new AmqpAutoRecoveringChannel(connectionManager); try { - amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, "fanout", false /* durable */, false /* auto-delete */, null); + amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, "fanout", false /* durable */, true /* auto-delete */, null); } catch (IOException e) { throw new ChannelException("Failed to setup channel from ActiveMQ connection", e); } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java index 0a57ab8c..044db397 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java @@ -49,7 +49,7 @@ public void testTopicExchangeCreated() throws Exception { adapter.subscribe((jsonMessage, ackHandler) -> { }); - verify(mockChannel).exchangeDeclare(topicName, "fanout", false, false, null); + verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null); } @Test(expected = RuntimeException.class) diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java index 70977980..7fb18aae 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java @@ -44,7 +44,7 @@ public void testExchangeCreated() throws IOException { new AmqpProducerAdapter(topicName, mockAmqpBrokerConfig, mockAmqpConnectionManager); - verify(mockChannel).exchangeDeclare(topicName, "fanout", false, false, null); + verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null); } @Test(expected = RuntimeException.class) From babe539a05a320ed3f0ec0be5db3569e7cb26f2a Mon Sep 17 00:00:00 2001 From: rkatsyuryna Date: Wed, 6 Jan 2016 12:30:12 +0200 Subject: [PATCH 038/226] Fix:channel monitor agent throws exceptions upon microservices shutdown --- .../src/test/resources/scenarios/channel_recreation.story | 5 ----- examples/src/main/resources/application.conf | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/acceptance/src/test/resources/scenarios/channel_recreation.story b/acceptance/src/test/resources/scenarios/channel_recreation.story index 7fb25663..5ebdd3df 100644 --- a/acceptance/src/test/resources/scenarios/channel_recreation.story +++ b/acceptance/src/test/resources/scenarios/channel_recreation.story @@ -2,7 +2,6 @@ Lifecycle: Before: Given init MSB context contextResponder And init MSB context contextRequester -And logger scanner reset After: Outcome: ANY Then shutdown context contextRequester @@ -18,10 +17,6 @@ And response equals |result| |hello jbehave| -When shutdown context contextResponder -And requester from contextRequester sends a request -Then log contains 'Shutdown is NOT initiated by application. Resetting the channel.' - When init MSB context contextResponder And responder server from contextResponder listens on namespace test:jbehave And responder server responds with '{"result": "hello jbehave"}' diff --git a/examples/src/main/resources/application.conf b/examples/src/main/resources/application.conf index c9f73d66..8cb3232f 100644 --- a/examples/src/main/resources/application.conf +++ b/examples/src/main/resources/application.conf @@ -6,5 +6,10 @@ msbConfig { version = "1.0.1" instanceId = "msbd06a-ed59-4a39-9f95-811c5fb6ab87" } + + # Broker Adapter Defaults + brokerConfig = { + durable = true + } } From a0e126bff041b24f7b43933967d8623b13fedff0 Mon Sep 17 00:00:00 2001 From: rdrozdov-tc Date: Wed, 6 Jan 2016 13:52:40 +0200 Subject: [PATCH 039/226] empty PR to fix build --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9ecf899b..f4eef515 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ For this you'll need to add a `server` to the `servers` section of your settings [YOUR_API_TOKEN] ``` - ## Bintray / jcenter SNAPSHOT publishing configuration: If you're part of the tcdl bintray organization (https://bintray.com/tcdl) and have sufficient rights you can publish snapshots to jfrog / jcenter (http://oss.jfrog.org/artifactory/simple/oss-snapshot-local/io/github/tcdl/). From 058d723aef7bd3530348f3be790f741beb75edef Mon Sep 17 00:00:00 2001 From: rdrozdov-tc Date: Wed, 6 Jan 2016 14:12:06 +0200 Subject: [PATCH 040/226] empty PR#2 to fix build --- acceptance/pom.xml | 2 -- cli/README.md | 2 +- core/pom.xml | 1 + examples/pom.xml | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 8f26659c..5d2af3ce 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -21,7 +21,6 @@ tcdl https://github.com/tcdl - io.github.tcdl.msb @@ -42,7 +41,6 @@ test - diff --git a/cli/README.md b/cli/README.md index 9fb2529d..afea8bb3 100644 --- a/cli/README.md +++ b/cli/README.md @@ -1,6 +1,6 @@ # MSB-Java CLI -Microservice bus - Java CLI +Microservice bus - Java CLI ## Build: ``` diff --git a/core/pom.xml b/core/pom.xml index c271380e..db670090 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,6 +21,7 @@ tcdl https://github.com/tcdl + org.apache.commons diff --git a/examples/pom.xml b/examples/pom.xml index 886d8136..1ae9f66b 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -17,6 +17,7 @@ https://github.com/tcdl/msb-java HEAD + tcdl https://github.com/tcdl From 86fbb4ce44a48bfab7d4f7c28960476f1375609a Mon Sep 17 00:00:00 2001 From: rdrozdov-tc Date: Wed, 6 Jan 2016 13:52:40 +0200 Subject: [PATCH 041/226] empty PR to fix build --- README.md | 1 - acceptance/pom.xml | 2 -- cli/README.md | 2 +- core/pom.xml | 1 + examples/pom.xml | 1 + 5 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9ecf899b..f4eef515 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ For this you'll need to add a `server` to the `servers` section of your settings [YOUR_API_TOKEN] ``` - ## Bintray / jcenter SNAPSHOT publishing configuration: If you're part of the tcdl bintray organization (https://bintray.com/tcdl) and have sufficient rights you can publish snapshots to jfrog / jcenter (http://oss.jfrog.org/artifactory/simple/oss-snapshot-local/io/github/tcdl/). diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 8f26659c..5d2af3ce 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -21,7 +21,6 @@ tcdl https://github.com/tcdl - io.github.tcdl.msb @@ -42,7 +41,6 @@ test - diff --git a/cli/README.md b/cli/README.md index 9fb2529d..afea8bb3 100644 --- a/cli/README.md +++ b/cli/README.md @@ -1,6 +1,6 @@ # MSB-Java CLI -Microservice bus - Java CLI +Microservice bus - Java CLI ## Build: ``` diff --git a/core/pom.xml b/core/pom.xml index c271380e..db670090 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,6 +21,7 @@ tcdl https://github.com/tcdl + org.apache.commons diff --git a/examples/pom.xml b/examples/pom.xml index 886d8136..1ae9f66b 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -17,6 +17,7 @@ https://github.com/tcdl/msb-java HEAD + tcdl https://github.com/tcdl From e8683a13401f78e383a9682f6c9ad2059160a5cd Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 11 Jan 2016 11:35:23 +0200 Subject: [PATCH 042/226] WEB-15194 integration testing Maven configuration --- acceptance/pom.xml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 5d2af3ce..b1d3e1b0 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -56,5 +56,25 @@ - + + + IntegrationTesting + + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + **/*Runner.class + + + + + + + \ No newline at end of file From 8ce316d3d7c1bfb9dc09439b72f9f1ca33e43ea5 Mon Sep 17 00:00:00 2001 From: rdrozdov-tc Date: Mon, 11 Jan 2016 14:14:52 +0200 Subject: [PATCH 043/226] not mandatory ack and payload fields --- core/src/main/java/io/github/tcdl/msb/api/message/Message.java | 2 -- core/src/main/resources/schema.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/api/message/Message.java b/core/src/main/java/io/github/tcdl/msb/api/message/Message.java index 191e70fd..1d8eb68e 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/message/Message.java +++ b/core/src/main/java/io/github/tcdl/msb/api/message/Message.java @@ -28,9 +28,7 @@ public final class Message { private final Topics topics; @JsonInclude(ALWAYS) private final MetaMessage meta; // To be filled with createMeta() ->completeMeta() sequence - @JsonInclude(ALWAYS) private final Acknowledge ack; // To be filled on ack or response - @JsonInclude(ALWAYS) @JsonProperty("payload") private final JsonNode rawPayload; diff --git a/core/src/main/resources/schema.js b/core/src/main/resources/schema.js index 6d627bc6..3324b250 100644 --- a/core/src/main/resources/schema.js +++ b/core/src/main/resources/schema.js @@ -30,7 +30,7 @@ "required": ["createdAt"] } }, - "required": ["id", "correlationId", "meta", "ack", "payload"], + "required": ["id", "correlationId", "meta"], "definitions": { "topic": { "type": ["string", "null"], From 1ac58ab282817adb61a41b0f6b4d8507beda453e Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 11 Jan 2016 15:45:05 +0200 Subject: [PATCH 044/226] WEB-14726 logging fix --- .../io/github/tcdl/msb/ChannelManager.java | 3 +- .../java/io/github/tcdl/msb/Consumer.java | 34 +++++++++++-------- .../tcdl/msb/MessageHandlerResolver.java | 7 ++++ .../tcdl/msb/collector/CollectorManager.java | 7 +++- .../SimpleMessageHandlerResolverImpl.java | 13 +++++-- ...eMessageHandlerInvokeStrategyImplTest.java | 4 +-- .../SimpleMessageHandlerResolverImplTest.java | 7 +++- 7 files changed, 53 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java index 6a491757..9d1c1ded 100644 --- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java +++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java @@ -26,6 +26,7 @@ public class ChannelManager { private static final Logger LOG = LoggerFactory.getLogger(ChannelManager.class); + private static final String RESPONDER_LOGGING_NAME = "Responder server"; private MsbConfig msbConfig; private Clock clock; @@ -74,7 +75,7 @@ public synchronized boolean subscribe(String topic, MessageHandler messageHandle if (consumersByTopic.get(topic) != null) { throw new ConsumerSubscriptionException("Subscriber for this topic: " + topic + " already exist"); } else { - Consumer newConsumer = createConsumer(topic, false, new SimpleMessageHandlerResolverImpl(messageHandler)); + Consumer newConsumer = createConsumer(topic, false, new SimpleMessageHandlerResolverImpl(messageHandler, RESPONDER_LOGGING_NAME)); channelMonitorAgent.consumerTopicCreated(topic); consumersByTopic.put(topic, newConsumer); return false; diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index da138ef2..c1f3d11b 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -32,12 +32,13 @@ public class Consumer { private final ConsumerAdapter rawAdapter; private final MessageHandlerInvokeStrategy messageHandlerInvokeStrategy; private final String topic; - private MsbConfig msbConfig; - private ChannelMonitorAgent channelMonitorAgent; - private Clock clock; - private MessageHandlerResolver messageHandlerResolver; - private JsonValidator validator; - private ObjectMapper messageMapper; + private final MsbConfig msbConfig; + private final ChannelMonitorAgent channelMonitorAgent; + private final Clock clock; + private final MessageHandlerResolver messageHandlerResolver; + private final JsonValidator validator; + private final ObjectMapper messageMapper; + private final String loggingTag; /** * @param rawAdapter instance of {@link ConsumerAdapter} that allows to receive messages from message bus @@ -75,13 +76,15 @@ public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvokeStrategy message this.messageMapper = messageMapper; this.rawAdapter.subscribe(this::handleRawMessage); + + this.loggingTag = String.format("[Consumer for: '%s' on topic: '%s']", messageHandlerResolver.getLoggingName(), topic); } /** * Stop consuming messages for specified topic. */ public void end() { - LOG.debug("Shutting down consumer for topic {}", topic); + LOG.debug("{} Shutting down consumer for topic {}", loggingTag, topic); rawAdapter.unsubscribe(); } @@ -92,7 +95,8 @@ public void end() { * @param jsonMessage message to process */ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerInternal acknowledgeHandler) { - LOG.debug("Topic [{}] message received [{}]", this.topic, jsonMessage); + LOG.debug("{} message received [{}]", loggingTag, jsonMessage); + channelMonitorAgent.consumerMessageReceived(topic); Message message; @@ -100,13 +104,13 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern try { message = parseMessage(jsonMessage); } catch (Exception e) { - LOG.error("Unable to process consumed message {}", jsonMessage, e); + LOG.error("{} Unable to process consumed message {}", loggingTag, jsonMessage, e); acknowledgeHandler.autoReject(); return; } if (isMessageExpired(message)) { - LOG.warn("Expired message: {}", jsonMessage); + LOG.warn("{} Expired message: {}", loggingTag, jsonMessage); acknowledgeHandler.autoReject(); return; } @@ -122,11 +126,11 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern } messageHandlerInvokeStrategy.execute(messageHandler, message, acknowledgeHandler); } else { - LOG.warn("Cant't resolve message handler for a message: {}", jsonMessage); + LOG.warn("{} Cant't resolve message handler for a message: {}", loggingTag, jsonMessage); acknowledgeHandler.autoReject(); } } catch (Exception e) { - LOG.warn("Error while trying to handle a message: {}", jsonMessage, e); + LOG.warn("{} Error while trying to handle a message: {}", loggingTag, jsonMessage, e); acknowledgeHandler.autoRetry(); if(consumedMessagesAwareMessageHandler != null) { consumedMessagesAwareMessageHandler.notifyConsumedMessageIsLost(); @@ -136,12 +140,12 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern private Message parseMessage(String jsonMessage) { if (msbConfig.getSchema() != null && !Utils.isServiceTopic(topic) && msbConfig.isValidateMessage()) { - LOG.debug("Validating schema for {}", jsonMessage); + LOG.debug("{} Validating schema for {}", loggingTag, jsonMessage); validator.validate(jsonMessage, msbConfig.getSchema()); } - LOG.debug("Parsing message {}", jsonMessage); + LOG.debug("{} Parsing message {}", loggingTag, jsonMessage); Message result = Utils.fromJson(jsonMessage, Message.class, messageMapper); - LOG.debug("Message has been successfully parsed {}", jsonMessage); + LOG.debug("{} Message has been successfully parsed {}", loggingTag, jsonMessage); return result; } diff --git a/core/src/main/java/io/github/tcdl/msb/MessageHandlerResolver.java b/core/src/main/java/io/github/tcdl/msb/MessageHandlerResolver.java index 124e16fb..62d0938a 100644 --- a/core/src/main/java/io/github/tcdl/msb/MessageHandlerResolver.java +++ b/core/src/main/java/io/github/tcdl/msb/MessageHandlerResolver.java @@ -15,4 +15,11 @@ public interface MessageHandlerResolver { * @return */ Optional resolveMessageHandler(Message message); + + /** + * Get an arbitrary text name of the MessageHandlerResolver instance that + * will be used used in log messages. + * @return + */ + String getLoggingName(); } diff --git a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java index bc48a0c0..0088f3f2 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java @@ -3,7 +3,6 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.MessageHandlerResolver; -import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException; import io.github.tcdl.msb.api.message.Message; @@ -20,6 +19,7 @@ public class CollectorManager implements MessageHandlerResolver { private static final Logger LOG = LoggerFactory.getLogger(CollectorManager.class); + private static final String LOGGING_NAME = "Collector manager"; private volatile boolean isSubscribed = false; @@ -74,4 +74,9 @@ public void unregisterCollector(Collector collector) { } } } + + @Override + public String getLoggingName() { + return LOGGING_NAME; + } } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImpl.java index c12ff9a0..3be5c426 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImpl.java @@ -14,11 +14,20 @@ public class SimpleMessageHandlerResolverImpl implements MessageHandlerResolver private final MessageHandler messageHandler; - public SimpleMessageHandlerResolverImpl(MessageHandler messageHandler) { + private final String loggingName; + + public SimpleMessageHandlerResolverImpl(MessageHandler messageHandler, String loggingName) { this.messageHandler = messageHandler; + this.loggingName = loggingName; } - @Override public Optional resolveMessageHandler(Message message) { + @Override + public Optional resolveMessageHandler(Message message) { return Optional.of(messageHandler); } + + @Override + public String getLoggingName() { + return loggingName; + } } diff --git a/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImplTest.java index 354a1b6b..04cf8cca 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImplTest.java @@ -22,11 +22,11 @@ public class SimpleMessageHandlerInvokeStrategyImplTest { Message message; @InjectMocks - SimpleMessageHandlerInvokeStrategyImpl adapter; + SimpleMessageHandlerInvokeStrategyImpl strategy; @Test public void testDirectInvoke() { - adapter.execute(messageHandler, message, acknowledgeHandler); + strategy.execute(messageHandler, message, acknowledgeHandler); verify(messageHandler, times(1)).handleMessage(message, acknowledgeHandler); verify(acknowledgeHandler, times(1)).autoConfirm(); } diff --git a/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImplTest.java index 5d1364c0..d6932f4d 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerResolverImplTest.java @@ -9,11 +9,15 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import java.util.UUID; + import static org.junit.Assert.assertEquals; @RunWith(MockitoJUnitRunner.class) public class SimpleMessageHandlerResolverImplTest { + private final static String LOGGING_NAME = UUID.randomUUID().toString(); + @Mock MessageHandler messageHandler; @@ -24,12 +28,13 @@ public class SimpleMessageHandlerResolverImplTest { @Before public void setUp() { message = TestUtils.createSimpleResponseMessage("any"); - resolver = new SimpleMessageHandlerResolverImpl(messageHandler); + resolver = new SimpleMessageHandlerResolverImpl(messageHandler, LOGGING_NAME); } @Test public void testMessageHandlerResolutionByAnyMessage() { assertEquals(messageHandler, resolver.resolveMessageHandler(message).get()); + assertEquals(LOGGING_NAME, resolver.getLoggingName()); } } From 92c57db59e811fe243784fc8bbb20cb344c11506 Mon Sep 17 00:00:00 2001 From: rdrozdov-tc Date: Mon, 11 Jan 2016 15:46:13 +0200 Subject: [PATCH 045/226] Added a test for null payload --- .../tcdl/msb/api/RequesterResponderIT.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java index 3c33fba5..e26384d2 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java @@ -1,6 +1,7 @@ package io.github.tcdl.msb.api; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import io.github.tcdl.msb.adapters.mock.MockAdapter; import io.github.tcdl.msb.api.message.Acknowledge; @@ -62,6 +63,25 @@ public void testResponderServerReceiveCustomPayloadMessageSendByRequester() thro assertTrue("Message was not received", requestReceived.await(MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); } + @Test + public void testResponderServerReceiveNullPayloadMessageSendByRequester() throws Exception { + String namespace = "test:requester-responder-test-null-request-received"; + RequestOptions requestOptions = TestUtils.createSimpleRequestOptions(); + CountDownLatch requestReceived = new CountDownLatch(1); + + //Create and send request message + Requester requester = msbContext.getObjectFactory().createRequester(namespace, requestOptions); + + msbContext.getObjectFactory().createResponderServer(namespace, requestOptions.getMessageTemplate(), (request, response) -> { + requestReceived.countDown(); + assertNull(request); + }, String.class).listen(); + + requester.publish(null); + + assertTrue("Message was not received", requestReceived.await(MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); + } + @Test public void testResponderAnswerWithAckRequesterReceiveAck() throws Exception { String namespace = "test:requester-responder-test-get-ack"; From 89885c6090ba6a5f882fee0639d5d16d390707d5 Mon Sep 17 00:00:00 2001 From: rkatsyuryna Date: Mon, 4 Jan 2016 13:29:34 +0200 Subject: [PATCH 046/226] Use of logback in microservices --- acceptance/src/main/resources/log4j.xml | 26 -------------- .../msb/acceptance/bdd/steps/LoggerSteps.java | 10 +++--- .../msb/acceptance/bdd/util/ListAppender.java | 34 ++++++------------- acceptance/src/test/resources/log4j.xml | 25 -------------- amqp/src/test/resources/log4j.xml | 17 ---------- cli/src/main/resources/log4j.xml | 17 ---------- cli/src/main/resources/logback.xml | 24 +++++++++++++ cli/src/test/resources/log4j.xml | 17 ---------- core/src/test/resources/log4j.xml | 17 ---------- core/src/test/resources/logback-test.xml | 15 ++++++++ examples/src/main/resources/log4j.xml | 26 -------------- pom.xml | 10 +++--- 12 files changed, 60 insertions(+), 178 deletions(-) delete mode 100644 acceptance/src/main/resources/log4j.xml delete mode 100644 acceptance/src/test/resources/log4j.xml delete mode 100644 amqp/src/test/resources/log4j.xml delete mode 100644 cli/src/main/resources/log4j.xml create mode 100644 cli/src/main/resources/logback.xml delete mode 100644 cli/src/test/resources/log4j.xml delete mode 100644 core/src/test/resources/log4j.xml create mode 100644 core/src/test/resources/logback-test.xml delete mode 100644 examples/src/main/resources/log4j.xml diff --git a/acceptance/src/main/resources/log4j.xml b/acceptance/src/main/resources/log4j.xml deleted file mode 100644 index ee87ca65..00000000 --- a/acceptance/src/main/resources/log4j.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java index f5372033..c3276595 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java @@ -6,14 +6,14 @@ import org.junit.Assert; public class LoggerSteps { - @Given("logger scanner reset") - public void start() { - ListAppender listAppender = ListAppender.getInstance(); - listAppender.reset(); - } @Then("log contains '$substring'") public void logContains(String substring) throws Exception { Assert.assertNotNull("String not found '" + substring + "'", ListAppender.getInstance().findLine(substring, 5, 500)); } + + @Given("clear log") + public void clearLog() throws Exception { + ListAppender.getInstance().reset(); + } } diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/ListAppender.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/ListAppender.java index c56c01c9..5ac0f0bb 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/ListAppender.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/ListAppender.java @@ -1,41 +1,29 @@ package io.github.tcdl.msb.acceptance.bdd.util; -import org.apache.log4j.AppenderSkeleton; -import org.apache.log4j.spi.LoggingEvent; -import org.junit.Assert; - import java.util.Optional; import java.util.concurrent.ConcurrentLinkedQueue; -public class ListAppender extends AppenderSkeleton { +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import org.junit.Assert; - private ConcurrentLinkedQueue logEntries = new ConcurrentLinkedQueue<>(); - private static ListAppender instance; +public class ListAppender extends AppenderBase { - public ListAppender() { - instance = this; - } + private volatile ConcurrentLinkedQueue logEntries = new ConcurrentLinkedQueue<>(); + private volatile static ListAppender instance = new ListAppender(); public static ListAppender getInstance() { - Assert.assertNotNull("ListAppender has not yet been initialized. Did you include it in you log4j config?"); + Assert.assertNotNull("ListAppender has not yet been initialized. Did you include it in you logback config?"); return instance; } @Override - protected void append(LoggingEvent event) { - logEntries.add(event.getMessage().toString()); - } - - public void close() { - logEntries.clear(); - } - - public boolean requiresLayout() { - return false; + protected void append(ILoggingEvent event) { + instance.logEntries.add(event.toString()); } public void reset() { - logEntries.clear(); + instance.logEntries.clear(); } public String findLine(String string, int retries, long pollIntervalMs) throws Exception { @@ -44,7 +32,7 @@ public String findLine(String string, int retries, long pollIntervalMs) throws E do { Thread.sleep(pollIntervalMs); - line = logEntries.stream().filter(logEntry -> logEntry.contains(string)).distinct().findFirst(); + line = instance.logEntries.stream().filter(logEntry -> logEntry.contains(string)).distinct().findFirst(); } while (!line.isPresent() && numberOfRetries-- > 0); return line.orElse(null); diff --git a/acceptance/src/test/resources/log4j.xml b/acceptance/src/test/resources/log4j.xml deleted file mode 100644 index d09d95c0..00000000 --- a/acceptance/src/test/resources/log4j.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/amqp/src/test/resources/log4j.xml b/amqp/src/test/resources/log4j.xml deleted file mode 100644 index 693ccc76..00000000 --- a/amqp/src/test/resources/log4j.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cli/src/main/resources/log4j.xml b/cli/src/main/resources/log4j.xml deleted file mode 100644 index b5a43681..00000000 --- a/cli/src/main/resources/log4j.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cli/src/main/resources/logback.xml b/cli/src/main/resources/logback.xml new file mode 100644 index 00000000..86e858f5 --- /dev/null +++ b/cli/src/main/resources/logback.xml @@ -0,0 +1,24 @@ + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + + + + + logs/micro-services.log + + UTF-8 + %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + + + + + + + + + + \ No newline at end of file diff --git a/cli/src/test/resources/log4j.xml b/cli/src/test/resources/log4j.xml deleted file mode 100644 index ab8fed55..00000000 --- a/cli/src/test/resources/log4j.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/core/src/test/resources/log4j.xml b/core/src/test/resources/log4j.xml deleted file mode 100644 index da0ed90a..00000000 --- a/core/src/test/resources/log4j.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9b42aaaf --- /dev/null +++ b/core/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + + + + + + + + + \ No newline at end of file diff --git a/examples/src/main/resources/log4j.xml b/examples/src/main/resources/log4j.xml deleted file mode 100644 index 0361fe73..00000000 --- a/examples/src/main/resources/log4j.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index e2fa2062..ce10160e 100644 --- a/pom.xml +++ b/pom.xml @@ -90,9 +90,9 @@ 1.7.10 - org.slf4j - slf4j-log4j12 - 1.7.10 + ch.qos.logback + logback-classic + 1.1.3 junit @@ -158,8 +158,8 @@ slf4j-api - org.slf4j - slf4j-log4j12 + ch.qos.logback + logback-classic runtime From 491d2c5db849f614d7ba380492d005e236fab838 Mon Sep 17 00:00:00 2001 From: rkatsyuryna Date: Tue, 5 Jan 2016 14:24:27 +0200 Subject: [PATCH 047/226] Use of logback in microservices. Add missing configs after rebase --- acceptance/pom.xml | 2 +- .../src/test/resources/logback-test.xml | 23 +++++++++++++++++++ .../scenarios/channel_recreation.story | 1 + amqp/pom.xml | 2 +- cli/pom.xml | 7 +++++- core/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 4 ++-- 8 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 acceptance/src/test/resources/logback-test.xml diff --git a/acceptance/pom.xml b/acceptance/pom.xml index b1d3e1b0..f7df0188 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0-WEB-16935-SNAPSHOT ../pom.xml 4.0.0 diff --git a/acceptance/src/test/resources/logback-test.xml b/acceptance/src/test/resources/logback-test.xml new file mode 100644 index 00000000..c6d71632 --- /dev/null +++ b/acceptance/src/test/resources/logback-test.xml @@ -0,0 +1,23 @@ + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + + + + + + + + + + \ No newline at end of file diff --git a/acceptance/src/test/resources/scenarios/channel_recreation.story b/acceptance/src/test/resources/scenarios/channel_recreation.story index 5ebdd3df..1aebcd19 100644 --- a/acceptance/src/test/resources/scenarios/channel_recreation.story +++ b/acceptance/src/test/resources/scenarios/channel_recreation.story @@ -2,6 +2,7 @@ Lifecycle: Before: Given init MSB context contextResponder And init MSB context contextRequester +And clear log After: Outcome: ANY Then shutdown context contextRequester diff --git a/amqp/pom.xml b/amqp/pom.xml index 87a7b64e..217965ea 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0-WEB-16935-SNAPSHOT ../pom.xml 4.0.0 diff --git a/cli/pom.xml b/cli/pom.xml index ea27499f..c211d658 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0-WEB-16935-SNAPSHOT ../pom.xml 4.0.0 @@ -30,6 +30,11 @@ io.github.tcdl.msb msb-java-amqp + + ch.qos.logback + logback-classic + runtime + diff --git a/core/pom.xml b/core/pom.xml index db670090..97971654 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0-WEB-16935-SNAPSHOT ../pom.xml 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index 1ae9f66b..2988609a 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0-WEB-16935-SNAPSHOT ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index ce10160e..f4085251 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0-WEB-16935-SNAPSHOT msb java msb java pom @@ -160,7 +160,7 @@ ch.qos.logback logback-classic - runtime + test From 3acc9690c2c0420f788828f1248485950d8ca736 Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 12 Jan 2016 10:21:33 +0200 Subject: [PATCH 048/226] WEB-17254 branch version --- acceptance/pom.xml | 2 +- amqp/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index f7df0188..262e9716 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-WEB-16935-SNAPSHOT + 1.4.0-WEB-17254-SNAPSHOT ../pom.xml 4.0.0 diff --git a/amqp/pom.xml b/amqp/pom.xml index 217965ea..b3ab1485 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-WEB-16935-SNAPSHOT + 1.4.0-WEB-17254-SNAPSHOT ../pom.xml 4.0.0 diff --git a/cli/pom.xml b/cli/pom.xml index c211d658..7ff8072c 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-WEB-16935-SNAPSHOT + 1.4.0-WEB-17254-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index 97971654..2246c5ab 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-WEB-16935-SNAPSHOT + 1.4.0-WEB-17254-SNAPSHOT ../pom.xml 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index 2988609a..aed29cf2 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-WEB-16935-SNAPSHOT + 1.4.0-WEB-17254-SNAPSHOT ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index f4085251..8f2f62e7 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.0-WEB-16935-SNAPSHOT + 1.4.0-WEB-17254-SNAPSHOT msb java msb java pom From 194f545ef9491794b9e6356667cc56d2a93b020c Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 12 Jan 2016 13:13:29 +0200 Subject: [PATCH 049/226] WEB-17254 logback migration --- acceptance/src/test/resources/logback-test.xml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/acceptance/src/test/resources/logback-test.xml b/acceptance/src/test/resources/logback-test.xml index c6d71632..61367f89 100644 --- a/acceptance/src/test/resources/logback-test.xml +++ b/acceptance/src/test/resources/logback-test.xml @@ -3,16 +3,11 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + %yellow(%d{HH:mm:ss.SSS}) %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n - - - UTF-8 - %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n - - + From 866af590073944c18317bb9316599e8d8db73826 Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 12 Jan 2016 17:58:01 +0200 Subject: [PATCH 050/226] WEB-17254 logback migration --- .travis.yml | 2 -- acceptance/pom.xml | 2 +- acceptance/src/test/resources/logback-test.xml | 1 + amqp/pom.xml | 3 ++- cli/pom.xml | 2 +- core/pom.xml | 2 +- core/src/test/resources/logback-test.xml | 5 +++-- examples/pom.xml | 2 +- pom.xml | 2 +- 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 41380ead..e7ec4011 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,6 @@ services: install: '' script: - mvn clean deploy --settings settings.xml -B -V -- mvn -Dtest=io.github.tcdl.msb.acceptance.* -DfailIfNoTests=false test -- mvn -Dtest=io.github.tcdl.msb.acceptance.bdd.* -DfailIfNoTests=false test after_success: - mvn clean test jacoco:report coveralls:report env: diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 262e9716..7e286626 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-WEB-17254-SNAPSHOT + 1.5.0-WEB-17254-SNAPSHOT ../pom.xml 4.0.0 diff --git a/acceptance/src/test/resources/logback-test.xml b/acceptance/src/test/resources/logback-test.xml index 61367f89..2f39f90f 100644 --- a/acceptance/src/test/resources/logback-test.xml +++ b/acceptance/src/test/resources/logback-test.xml @@ -1,6 +1,7 @@ + true UTF-8 %yellow(%d{HH:mm:ss.SSS}) %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n diff --git a/amqp/pom.xml b/amqp/pom.xml index b3ab1485..578218d0 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-WEB-17254-SNAPSHOT + 1.5.0-WEB-17254-SNAPSHOT ../pom.xml 4.0.0 @@ -30,6 +30,7 @@ io.github.tcdl.msb msb-java-core test-jar + test com.rabbitmq diff --git a/cli/pom.xml b/cli/pom.xml index 7ff8072c..8427dfba 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-WEB-17254-SNAPSHOT + 1.5.0-WEB-17254-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index 2246c5ab..c90c350d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-WEB-17254-SNAPSHOT + 1.5.0-WEB-17254-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml index 9b42aaaf..969c313c 100644 --- a/core/src/test/resources/logback-test.xml +++ b/core/src/test/resources/logback-test.xml @@ -1,9 +1,10 @@ - + + true UTF-8 - %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + %yellow(%d{HH:mm:ss.SSS}) %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n diff --git a/examples/pom.xml b/examples/pom.xml index aed29cf2..66deff7d 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-WEB-17254-SNAPSHOT + 1.5.0-WEB-17254-SNAPSHOT ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index 8f2f62e7..0d93a1f7 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.0-WEB-17254-SNAPSHOT + 1.5.0-WEB-17254-SNAPSHOT msb java msb java pom From 5832fb38aa89f4f3669348616b30f76784cfb45f Mon Sep 17 00:00:00 2001 From: anha1 Date: Wed, 13 Jan 2016 11:30:35 +0200 Subject: [PATCH 051/226] WEB-17254 logback migration --- .../msb/acceptance/bdd/steps/LoggerSteps.java | 7 +- .../msb/acceptance/bdd/util/ListAppender.java | 40 ----------- .../bdd/util/TestOutputStreamAppender.java | 68 +++++++++++++++++++ .../src/test/resources/logback-test.xml | 17 +++-- cli/src/main/resources/logback.xml | 13 ++-- core/src/test/resources/logback-test.xml | 2 +- 6 files changed, 93 insertions(+), 54 deletions(-) delete mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/ListAppender.java create mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/TestOutputStreamAppender.java diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java index c3276595..b5b53474 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java @@ -1,6 +1,7 @@ package io.github.tcdl.msb.acceptance.bdd.steps; -import io.github.tcdl.msb.acceptance.bdd.util.ListAppender; + +import io.github.tcdl.msb.acceptance.bdd.util.TestOutputStreamAppender; import org.jbehave.core.annotations.Given; import org.jbehave.core.annotations.Then; import org.junit.Assert; @@ -9,11 +10,11 @@ public class LoggerSteps { @Then("log contains '$substring'") public void logContains(String substring) throws Exception { - Assert.assertNotNull("String not found '" + substring + "'", ListAppender.getInstance().findLine(substring, 5, 500)); + Assert.assertNotNull("String not found '" + substring + "'", TestOutputStreamAppender.isPresent(substring, 5, 500)); } @Given("clear log") public void clearLog() throws Exception { - ListAppender.getInstance().reset(); + TestOutputStreamAppender.reset(); } } diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/ListAppender.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/ListAppender.java deleted file mode 100644 index 5ac0f0bb..00000000 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/ListAppender.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.tcdl.msb.acceptance.bdd.util; - -import java.util.Optional; -import java.util.concurrent.ConcurrentLinkedQueue; - -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.AppenderBase; -import org.junit.Assert; - -public class ListAppender extends AppenderBase { - - private volatile ConcurrentLinkedQueue logEntries = new ConcurrentLinkedQueue<>(); - private volatile static ListAppender instance = new ListAppender(); - - public static ListAppender getInstance() { - Assert.assertNotNull("ListAppender has not yet been initialized. Did you include it in you logback config?"); - return instance; - } - - @Override - protected void append(ILoggingEvent event) { - instance.logEntries.add(event.toString()); - } - - public void reset() { - instance.logEntries.clear(); - } - - public String findLine(String string, int retries, long pollIntervalMs) throws Exception { - Optional line; - int numberOfRetries = retries; - - do { - Thread.sleep(pollIntervalMs); - line = instance.logEntries.stream().filter(logEntry -> logEntry.contains(string)).distinct().findFirst(); - } while (!line.isPresent() && numberOfRetries-- > 0); - - return line.orElse(null); - } -} \ No newline at end of file diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/TestOutputStreamAppender.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/TestOutputStreamAppender.java new file mode 100644 index 00000000..c9263ce3 --- /dev/null +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/util/TestOutputStreamAppender.java @@ -0,0 +1,68 @@ +package io.github.tcdl.msb.acceptance.bdd.util; + +import ch.qos.logback.core.OutputStreamAppender; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * This appender writes log messages into an internal buffer + * so it is possible to query log for particular messages + * during integration testing. + */ +public class TestOutputStreamAppender extends OutputStreamAppender { + + private final static ByteArrayOutputStream OUT = new ByteArrayOutputStream(); + private final static BufferedOutputStream OUT_BUFFERED = new BufferedOutputStream(OUT); + + /** + * Clean an internal text buffer. + * @throws Exception + */ + public synchronized static void reset() throws Exception { + OUT_BUFFERED.flush(); + OUT.flush(); + OUT.reset(); + } + + /** + * Substring lookup in an internal text buffer. + * @param substring + * @param retries + * @param pollIntervalMs + * @return + * @throws Exception + */ + public synchronized static boolean isPresent(String substring, int retries, long pollIntervalMs) throws Exception { + do { + if(OUT.toString().contains(substring)) { + return true; + } + Thread.sleep(pollIntervalMs); + } while (retries --> 0); + return false; + } + + @Override + public void start() { + setOutputStream(OUT_BUFFERED); + super.start(); + } + + @Override + public void stop() { + try { + OUT_BUFFERED.close(); + OUT.close(); + } catch (IOException ex) { + //ignore + } + super.stop(); + } + + @Override + protected void writeOut(E event) throws IOException { + super.writeOut(event); + } +} diff --git a/acceptance/src/test/resources/logback-test.xml b/acceptance/src/test/resources/logback-test.xml index 2f39f90f..81d18594 100644 --- a/acceptance/src/test/resources/logback-test.xml +++ b/acceptance/src/test/resources/logback-test.xml @@ -1,19 +1,26 @@ - + true UTF-8 - %yellow(%d{HH:mm:ss.SSS}) %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n + %yellow(%d{HH:mm:ss.SSS}) %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n - + + + UTF-8 + %-5level %m%n + + + + + + - - \ No newline at end of file diff --git a/cli/src/main/resources/logback.xml b/cli/src/main/resources/logback.xml index 86e858f5..0835c77f 100644 --- a/cli/src/main/resources/logback.xml +++ b/cli/src/main/resources/logback.xml @@ -1,9 +1,10 @@ - + + true UTF-8 - %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + %yellow(%d{HH:mm:ss.SSS}) %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n @@ -11,14 +12,16 @@ logs/micro-services.log UTF-8 - %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} %m%n + + + - + - \ No newline at end of file diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml index 969c313c..702d01de 100644 --- a/core/src/test/resources/logback-test.xml +++ b/core/src/test/resources/logback-test.xml @@ -4,7 +4,7 @@ true UTF-8 - %yellow(%d{HH:mm:ss.SSS}) %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n + %yellow(%d{HH:mm:ss.SSS}) %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n From 814668fff341212333b2fdc82a92244a5849d8c2 Mon Sep 17 00:00:00 2001 From: anha1 Date: Wed, 13 Jan 2016 16:03:15 +0200 Subject: [PATCH 052/226] WEB-17155 release notes --- release-notes.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/release-notes.html b/release-notes.html index 87a6ba7b..cafa1259 100644 --- a/release-notes.html +++ b/release-notes.html @@ -8,8 +8,7 @@

Welcome to MSB-Java version 1.4.0

December 7, 2015

-
MSB-Java version 1.4.0 is a maintenance release that fixes minor bags.
-It is also adds some new features.
+
MSB-Java version 1.4.0 contains bug fixes and adds new features.
 
 ------------------------------------------------------------------------------------
 

From cc144b2af359a1bea1417acdfcc6ab1246148477 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Wed, 13 Jan 2016 16:16:46 +0200
Subject: [PATCH 053/226] WEB-17155 release notes

---
 release-notes.html | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/release-notes.html b/release-notes.html
index cafa1259..5015216d 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -13,11 +13,12 @@ 

December 7, 2015

------------------------------------------------------------------------------------ Features of MSB-Java version 1.4.0: - - Added prefetch count for AMQP adapter; - (for details see https://www.rabbitmq.com/consumer-prefetch.html) + - Added prefetch count for AMQP adapter + (for details see https://www.rabbitmq.com/consumer-prefetch.html); - Implemented explicit confirm/reject delivered messages; - Fixed an issue when `onEnd` callback could be invoked by timeout while not all incoming responses are processed yet; - - Fixed an issue when time-consuming `onResponse` callbacks could block incoming responses processing. + - Fixed an issue when time-consuming `onResponse` callbacks could block incoming responses processing; + - Acceptance testing is enabled by default during a build. Features of MSB-Java version 1.3.0: - Removed REST-style payload constraint on parameters type; From 1e0238c00d0dfb299b2db8c6cee75797bbbfaddb Mon Sep 17 00:00:00 2001 From: anha1 Date: Wed, 13 Jan 2016 16:17:56 +0200 Subject: [PATCH 054/226] [maven-release-plugin] prepare release msb-java-1.4.0 --- acceptance/pom.xml | 4 ++-- amqp/pom.xml | 4 ++-- cli/pom.xml | 4 ++-- core/pom.xml | 4 ++-- examples/pom.xml | 4 ++-- pom.xml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index b1d3e1b0..43382c72 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.0 tcdl diff --git a/amqp/pom.xml b/amqp/pom.xml index 87a7b64e..d2b8e764 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.0 tcdl diff --git a/cli/pom.xml b/cli/pom.xml index ea27499f..3eec76b0 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.0 tcdl diff --git a/core/pom.xml b/core/pom.xml index db670090..d6d1337a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.0 tcdl diff --git a/examples/pom.xml b/examples/pom.xml index 1ae9f66b..115283bd 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.0 diff --git a/pom.xml b/pom.xml index e2fa2062..047ce9a4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.0-SNAPSHOT + 1.4.0 msb java msb java pom @@ -11,7 +11,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.0 From ed7e000f10238c133f04d1266ed27a9d3545cdc5 Mon Sep 17 00:00:00 2001 From: anha1 Date: Wed, 13 Jan 2016 16:18:13 +0200 Subject: [PATCH 055/226] [maven-release-plugin] prepare for next development iteration --- acceptance/pom.xml | 4 ++-- amqp/pom.xml | 4 ++-- cli/pom.xml | 4 ++-- core/pom.xml | 4 ++-- examples/pom.xml | 4 ++-- pom.xml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 43382c72..2cd9c232 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0 + 1.4.1-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.0 + HEAD tcdl diff --git a/amqp/pom.xml b/amqp/pom.xml index d2b8e764..7e7763f1 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0 + 1.4.1-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.0 + HEAD tcdl diff --git a/cli/pom.xml b/cli/pom.xml index 3eec76b0..44944820 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0 + 1.4.1-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.0 + HEAD tcdl diff --git a/core/pom.xml b/core/pom.xml index d6d1337a..47eb2e90 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0 + 1.4.1-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.0 + HEAD tcdl diff --git a/examples/pom.xml b/examples/pom.xml index 115283bd..4e85f11e 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.0 + 1.4.1-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.0 + HEAD diff --git a/pom.xml b/pom.xml index 047ce9a4..e3ab4ca5 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.0 + 1.4.1-SNAPSHOT msb java msb java pom @@ -11,7 +11,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.0 + HEAD From 135ea85f99fc71cdb119eb9535c6e7b7285c1dfe Mon Sep 17 00:00:00 2001 From: anha1 Date: Wed, 13 Jan 2016 16:50:46 +0200 Subject: [PATCH 056/226] WEB-17254 logback migration - version changes --- acceptance/pom.xml | 2 +- amqp/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 7e286626..2cd9c232 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.5.0-WEB-17254-SNAPSHOT + 1.4.1-SNAPSHOT ../pom.xml 4.0.0 diff --git a/amqp/pom.xml b/amqp/pom.xml index 578218d0..df08373e 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.5.0-WEB-17254-SNAPSHOT + 1.4.1-SNAPSHOT ../pom.xml 4.0.0 diff --git a/cli/pom.xml b/cli/pom.xml index 8427dfba..a2f1c311 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.5.0-WEB-17254-SNAPSHOT + 1.4.1-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index c90c350d..47eb2e90 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.5.0-WEB-17254-SNAPSHOT + 1.4.1-SNAPSHOT ../pom.xml 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index 66deff7d..4e85f11e 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.5.0-WEB-17254-SNAPSHOT + 1.4.1-SNAPSHOT ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index 0d93a1f7..af095279 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.5.0-WEB-17254-SNAPSHOT + 1.4.1-SNAPSHOT msb java msb java pom From b767ab2f86554ef66b89f38b90ed1d82cdb7cd6d Mon Sep 17 00:00:00 2001 From: anha1 Date: Wed, 13 Jan 2016 17:38:23 +0200 Subject: [PATCH 057/226] WEB-17155 release notes --- release-notes.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/release-notes.html b/release-notes.html index 5015216d..ad36273f 100644 --- a/release-notes.html +++ b/release-notes.html @@ -4,13 +4,17 @@ -

Welcome to MSB-Java version 1.4.0

+

Welcome to MSB-Java version 1.4.1

-

December 7, 2015

+

January 13, 2016

-
MSB-Java version 1.4.0 contains bug fixes and adds new features.
+
MSB-Java version 1.4.1 contains logging infrastructure changes.
 
 ------------------------------------------------------------------------------------
+Features of MSB-Java version 1.4.1:
+   - the library uses only slf4j API for logging
+     so log4j dependency is no longer included;
+   - logback dependency is included for testing only.
 
 Features of MSB-Java version 1.4.0:
    - Added prefetch count for AMQP adapter

From d9bdbf790bdfdb17339ca92d3ab131fdd6102b27 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Wed, 13 Jan 2016 17:41:39 +0200
Subject: [PATCH 058/226] [maven-release-plugin] prepare release msb-java-1.4.1

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 pom.xml            | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 2cd9c232..9e3f427f 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.1-SNAPSHOT
+        1.4.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.1
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index df08373e..61216154 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.1-SNAPSHOT
+        1.4.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.1
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index a2f1c311..64902257 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.1-SNAPSHOT
+        1.4.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.1
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 47eb2e90..f74d9e55 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.1-SNAPSHOT
+        1.4.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.1
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 4e85f11e..49a521f5 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.1-SNAPSHOT
+        1.4.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.1
     
 
     
diff --git a/pom.xml b/pom.xml
index af095279..bd8d4771 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.4.1-SNAPSHOT
+    1.4.1
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.1
     
     
         

From 64e7dc3647433afa5f2de2afd65854c6213851c5 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Wed, 13 Jan 2016 17:41:44 +0200
Subject: [PATCH 059/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 pom.xml            | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 9e3f427f..b28d349f 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.1
+        1.4.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.1
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 61216154..5b778fb1 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.1
+        1.4.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.1
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 64902257..1bfdc229 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.1
+        1.4.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.1
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index f74d9e55..08299179 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.1
+        1.4.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.1
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 49a521f5..8636e43e 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.1
+        1.4.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.1
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index bd8d4771..50f7da1f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.4.1
+    1.4.2-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.1
+        HEAD
     
     
         

From 349772a6e517960a0910620b56e087f41f0a1688 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Wed, 13 Jan 2016 17:43:01 +0200
Subject: [PATCH 060/226] [maven-release-plugin] rollback the release of
 msb-java-1.4.1

---
 acceptance/pom.xml | 2 +-
 amqp/pom.xml       | 2 +-
 cli/pom.xml        | 2 +-
 core/pom.xml       | 2 +-
 examples/pom.xml   | 2 +-
 pom.xml            | 2 +-
 6 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index b28d349f..2cd9c232 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.2-SNAPSHOT
+        1.4.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 5b778fb1..df08373e 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.2-SNAPSHOT
+        1.4.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/cli/pom.xml b/cli/pom.xml
index 1bfdc229..a2f1c311 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.2-SNAPSHOT
+        1.4.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/core/pom.xml b/core/pom.xml
index 08299179..47eb2e90 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.2-SNAPSHOT
+        1.4.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/examples/pom.xml b/examples/pom.xml
index 8636e43e..4e85f11e 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.2-SNAPSHOT
+        1.4.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/pom.xml b/pom.xml
index 50f7da1f..af095279 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.4.2-SNAPSHOT
+    1.4.1-SNAPSHOT
     msb java
     msb java
     pom

From b983d2f36fc766ab70d1c18d4f903a3bc77e92e2 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Wed, 13 Jan 2016 18:02:13 +0200
Subject: [PATCH 061/226] WEB-17254 logback migration - msb configuration fixes

---
 .../src/test/resources/logback-test.xml       |  8 ++------
 cli/src/main/resources/logback.xml            | 19 +++----------------
 core/src/test/resources/logback-test.xml      |  5 ++---
 3 files changed, 7 insertions(+), 25 deletions(-)

diff --git a/acceptance/src/test/resources/logback-test.xml b/acceptance/src/test/resources/logback-test.xml
index 81d18594..a3ee09e7 100644
--- a/acceptance/src/test/resources/logback-test.xml
+++ b/acceptance/src/test/resources/logback-test.xml
@@ -1,10 +1,9 @@
 
-
+
     
-        true
         
             UTF-8
-            %yellow(%d{HH:mm:ss.SSS})  %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n
+            %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n
         
     
 
@@ -15,9 +14,6 @@
         
     
 
-    
-    
-
     
         
         
diff --git a/cli/src/main/resources/logback.xml b/cli/src/main/resources/logback.xml
index 0835c77f..6e35c0af 100644
--- a/cli/src/main/resources/logback.xml
+++ b/cli/src/main/resources/logback.xml
@@ -1,27 +1,14 @@
 
-
+
     
-        true
         
             UTF-8
-            %yellow(%d{HH:mm:ss.SSS})  %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n
+            %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n
         
     
 
-    
-        logs/micro-services.log
-        
-            UTF-8
-            %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} %m%n
-        
-    
-
-    
-    
-
     
-        
+        
         
-        
     
 
\ No newline at end of file
diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml
index 702d01de..c9f72fc1 100644
--- a/core/src/test/resources/logback-test.xml
+++ b/core/src/test/resources/logback-test.xml
@@ -1,10 +1,9 @@
 
-
+
     
-        true
         
             UTF-8
-            %yellow(%d{HH:mm:ss.SSS})  %highlight(%-5level) %blue(%logger) %yellow([%t]) %blue(%c{1}) - %m%n
+            %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n
         
     
 

From c393deebea0b654cf0561e927692f3c6491e1c79 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Fri, 15 Jan 2016 13:12:12 +0200
Subject: [PATCH 062/226] dependencies versions updates, release notes

---
 .../msb/adapters/amqp/AmqpAdapterFactory.java |  9 ++------
 pom.xml                                       | 22 +++++++++----------
 release-notes.html                            | 17 +++++++-------
 3 files changed, 22 insertions(+), 26 deletions(-)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
index dd017cb1..c2274ca7 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
@@ -20,12 +20,7 @@
 
 import java.io.IOException;
 import java.util.Optional;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
 
 /**
  * AmqpAdapterFactory is an implementation of {@link AdapterFactory}
@@ -138,7 +133,7 @@ protected Connection createConnection(ConnectionFactory connectionFactory) {
             }
             LOG.info("AMQP connection opened.");
             return connection;
-        } catch (IOException e) {
+        } catch (IOException | TimeoutException e) {
             throw new ChannelException("Failed to obtain connection to AMQP broker", e);
         }
     }
diff --git a/pom.xml b/pom.xml
index af095279..9b7ea4a2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,12 +82,12 @@
             
                 com.typesafe
                 config
-                1.2.1
+                1.3.0
             
             
                 org.slf4j
                 slf4j-api
-                1.7.10
+                1.7.13
             
             
                 ch.qos.logback
@@ -102,17 +102,17 @@
             
                 org.assertj
                 assertj-core
-                3.2.0
+                3.3.0
             
             
                 org.jbehave
                 jbehave-core
-                4.0.1
+                4.0.4
             
             
                 com.fasterxml.jackson.datatype
                 jackson-datatype-jsr310
-                2.4.0
+                2.7.0
             
             
                 org.mockito
@@ -122,12 +122,12 @@
             
                 com.rabbitmq
                 amqp-client
-                3.5.1
+                3.6.0
             
             
                 com.googlecode.junit-toolbox
                 junit-toolbox
-                2.1
+                2.2
             
         
     
@@ -185,10 +185,10 @@
                     false
                     release
                     true
-        			
-            			pom.xml
-            			settings.xml
-        			
+                    
+                        pom.xml
+                        settings.xml
+                    
                 
             
             
diff --git a/release-notes.html b/release-notes.html
index ad36273f..b912b1dd 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -1,20 +1,21 @@
 
 
-  
+    
 
 
 
-

Welcome to MSB-Java version 1.4.1

+

Welcome to MSB-Java version 1.4.2

-

January 13, 2016

+

January 15, 2016

-
MSB-Java version 1.4.1 contains logging infrastructure changes.
+
MSB-Java version 1.4.2 contains logging infrastructure changes and dependencies updates.
 
 ------------------------------------------------------------------------------------
-Features of MSB-Java version 1.4.1:
+Features of MSB-Java version 1.4.2:
    - the library uses only slf4j API for logging
      so log4j dependency is no longer included;
-   - logback dependency is included for testing only.
+   - logback dependency is included for testing only;
+   - dependencies updates.
 
 Features of MSB-Java version 1.4.0:
    - Added prefetch count for AMQP adapter
@@ -30,5 +31,5 @@ 

January 13, 2016

`waitForResponses: 0`.
- - + + \ No newline at end of file From 829e9fad84ab980eeec7ba442c4ffd413c4a0748 Mon Sep 17 00:00:00 2001 From: anha1 Date: Wed, 27 Jan 2016 10:18:04 +0200 Subject: [PATCH 063/226] version fix --- acceptance/pom.xml | 2 +- amqp/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 2cd9c232..5efbeaab 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.1-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/amqp/pom.xml b/amqp/pom.xml index df08373e..e5a9524a 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.1-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/cli/pom.xml b/cli/pom.xml index a2f1c311..cc1d2708 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.1-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index 47eb2e90..6a1b0119 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.1-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index 4e85f11e..c25fe248 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.1-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index 9b7ea4a2..ac5c45c2 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.1-SNAPSHOT + 1.4.3-SNAPSHOT msb java msb java pom From e59b0fde881b66ab5b107c06f984ea48035453b1 Mon Sep 17 00:00:00 2001 From: anha1 Date: Wed, 27 Jan 2016 15:19:01 +0200 Subject: [PATCH 064/226] WEB-17718 MDC logging --- acceptance/pom.xml | 2 +- .../tcdl/msb/acceptance/MsbTestHelper.java | 11 +++- .../msb/acceptance/bdd/steps/LoggerSteps.java | 2 +- .../bdd/steps/RequesterResponderSteps.java | 7 +++ .../src/test/resources/logback-test.xml | 4 +- .../scenarios/requester_responder.story | 13 +++++ amqp/pom.xml | 2 +- .../amqp/AmqpMessageProcessingTask.java | 16 +++++- .../amqp/AmqpMessageProcessingTaskTest.java | 44 +++++++++++++--- cli/pom.xml | 2 +- core/pom.xml | 2 +- .../java/io/github/tcdl/msb/Consumer.java | 33 +++++++++--- .../io/github/tcdl/msb/config/MsbConfig.java | 50 ++++++++++++++----- core/src/test/resources/logback-test.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 2 +- 16 files changed, 157 insertions(+), 37 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 5efbeaab..dfd89458 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3-WEB-17718-SNAPSHOT ../pom.xml 4.0.0 diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java index 0e1606d4..72848e18 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java @@ -95,6 +95,10 @@ public void sendRequest(Requester requester, Object payload, Integer wait sendRequest(requester, payload, true, waitForResponses, null, responseCallback, null); } + public void sendRequest(Requester requester, Object payload, Integer waitForResponses, Callback responseCallback, Callback endCallback, String tag) throws Exception { + sendRequest(requester, payload, true, waitForResponses, null, responseCallback, endCallback, tag); + } + public void sendRequest(Requester requester, Object payload, Integer waitForResponses, Callback responseCallback, Callback endCallback) throws Exception { sendRequest(requester, payload, true, waitForResponses, null, responseCallback, endCallback); } @@ -107,6 +111,11 @@ public void sendRequest(Requester requester, Object payload, boolean wait public void sendRequest(Requester requester, Object payload, boolean waitForAck, Integer waitForResponses, Callback ackCallback, Callback responseCallback, Callback endCallback) throws Exception { + sendRequest(requester, payload, waitForAck, waitForResponses, ackCallback, responseCallback, endCallback, null); + } + + public void sendRequest(Requester requester, Object payload, boolean waitForAck, Integer waitForResponses, + Callback ackCallback, Callback responseCallback, Callback endCallback, String tag) throws Exception { requester.onAcknowledge((acknowledge, ackHandler) -> { System.out.println(">>> ACKNOWLEDGE: " + acknowledge); @@ -128,7 +137,7 @@ public void sendRequest(Requester requester, Object payload, boolean wait } }); - requester.publish(payload); + requester.publish(payload, tag); } public ResponderServer createResponderServer(String namespace, ResponderServer.RequestHandler requestHandler) { diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java index b5b53474..05de90e5 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java @@ -10,7 +10,7 @@ public class LoggerSteps { @Then("log contains '$substring'") public void logContains(String substring) throws Exception { - Assert.assertNotNull("String not found '" + substring + "'", TestOutputStreamAppender.isPresent(substring, 5, 500)); + Assert.assertTrue("String not found '" + substring + "'", TestOutputStreamAppender.isPresent(substring, 5, 500)); } @Given("clear log") diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java index e221b31c..6238b67a 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java @@ -177,6 +177,13 @@ public void sendRequest(String contextName) throws Exception { helper.sendRequest(requester, payload, responsesToExpectCount, this::onResponse, this::onEnd); } + @When("requester sends a request with tag '$tag'") + public void sendRequestWithTag(String tag) throws Exception { + onBeforeRequest(); + RestPayload payload = helper.createFacetParserPayload("QUERY", null); + helper.sendRequest(requester, payload, responsesToExpectCount, this::onResponse, this::onEnd, tag); + } + @When("requester sends a request with query '$query'") public void sendRequestWithQuery(String query) throws Exception { onBeforeRequest(); diff --git a/acceptance/src/test/resources/logback-test.xml b/acceptance/src/test/resources/logback-test.xml index a3ee09e7..2846e0a5 100644 --- a/acceptance/src/test/resources/logback-test.xml +++ b/acceptance/src/test/resources/logback-test.xml @@ -3,14 +3,14 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + %d{HH:mm:ss.SSS} %-5level %logger [%t] tags[%X{msbTags}] corrId[%X{msbCorrelationId}] - %m%n UTF-8 - %-5level %m%n + %-5level tags[%X{msbTags}] corrId[%X{msbCorrelationId}] %m%n diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story index 06be4db8..8e4916ed 100644 --- a/acceptance/src/test/resources/scenarios/requester_responder.story +++ b/acceptance/src/test/resources/scenarios/requester_responder.story @@ -2,6 +2,7 @@ Lifecycle: Before: Given MSB configuration with consumer prefetch count 20 And start MSB +And clear log After: Outcome: ANY Then shutdown MSB @@ -18,6 +19,18 @@ And response equals |result| |hello jbehave| +Scenario: Sends a request to a responder server with a custom tag + +Given responder server responds with '{"result": "hello jbehave"}' +And responder server listens on namespace test:jbehave +And requester sends requests to namespace test:jbehave +When requester sends a request with tag 'MY_CUSTOM_TAG' +Then requester gets response in 5000 ms +And responder requests received count equals 1 +And response equals +|result| +|hello jbehave| +And log contains 'MY_CUSTOM_TAG' Scenario: Responder ask to retry a first message delivery so it will be redelivered diff --git a/amqp/pom.xml b/amqp/pom.xml index e5a9524a..9df7f85b 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3-WEB-17718-SNAPSHOT ../pom.xml 4.0.0 diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java index 1f67e703..8a4176b2 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java @@ -1,11 +1,13 @@ package io.github.tcdl.msb.adapters.amqp; import io.github.tcdl.msb.MessageHandler; -import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; import io.github.tcdl.msb.api.message.Message; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import java.util.Map; /** * {@link AmqpMessageProcessingTask} wraps incoming message. This task is put into message processing thread pool (see {@link AmqpMessageConsumer}). @@ -16,12 +18,16 @@ public class AmqpMessageProcessingTask implements Runnable { final Message message; final MessageHandler messageHandler; final AcknowledgementHandlerInternal ackHandler; + final Map mdcLogContextMap; + final boolean mdcLogCopy; public AmqpMessageProcessingTask( MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal ackHandler) { this.message = message; this.messageHandler = messageHandler; this.ackHandler = ackHandler; + this.mdcLogContextMap = MDC.getCopyOfContextMap(); + this.mdcLogCopy = mdcLogContextMap != null && !mdcLogContextMap.isEmpty(); } /** @@ -31,6 +37,9 @@ public AmqpMessageProcessingTask( MessageHandler messageHandler, Message message */ @Override public void run() { + if(mdcLogCopy) { + MDC.setContextMap(mdcLogContextMap); + } try { LOG.debug(String.format("[correlation id: %s] Starting message processing", message.getCorrelationId())); messageHandler.handleMessage(message, ackHandler); @@ -39,7 +48,10 @@ public void run() { } catch (Exception e) { LOG.error(String.format("[correlation id: %s] Failed to process message", message.getCorrelationId()), e); ackHandler.autoRetry(); + } finally { + if(mdcLogCopy) { + MDC.clear(); + } } } - } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java index eca8f66b..5f63f865 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java @@ -1,31 +1,32 @@ package io.github.tcdl.msb.adapters.amqp; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.*; + import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.MessageHandler; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import java.io.Closeable; import java.io.IOException; +import java.util.concurrent.*; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl; import io.github.tcdl.msb.api.message.Message; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import com.rabbitmq.client.Channel; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.slf4j.MDC; @RunWith(MockitoJUnitRunner.class) public class AmqpMessageProcessingTaskTest { - - private String messageStr = "some message"; + private final String MDC_KEY = "key"; + private final String MDC_VALUE = "any"; private Message message; @Mock @@ -68,4 +69,35 @@ public void testExceptionDuringProcessing() { fail(); } } + + @Test + public void testMdcProvided() throws Exception { + try(Closeable mdcCloseable = MDC.putCloseable(MDC_KEY, MDC_VALUE)) { + assertTrue("MDC data is missing in a thread while was provided", isMdcPresentInThread()); + } + } + + @Test + public void testMdcNotProvided() throws Exception { + assertFalse("MDC data is present in a thread while was not provided", isMdcPresentInThread()); + } + + private boolean isMdcPresentInThread() throws Exception{ + CompletableFuture isMdcPresentInTaskRun = new CompletableFuture<>(); + CompletableFuture isMdcPresentInOtherRun = new CompletableFuture<>(); + MessageHandler mdcMessageHandler = (message, acknowledgeHandler) -> { + isMdcPresentInTaskRun.complete(isMdcPresent()); + }; + AmqpMessageProcessingTask mdcTask = + new AmqpMessageProcessingTask(mdcMessageHandler, message, mockAcknowledgementHandler); + ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); + singleThreadExecutor.execute(mdcTask); + singleThreadExecutor.execute(() -> isMdcPresentInOtherRun.complete(isMdcPresent())); + assertFalse("MDC data cleanup was not performed", isMdcPresentInOtherRun.get()); + return isMdcPresentInTaskRun.get(); + } + + private boolean isMdcPresent() { + return MDC_VALUE.equals(MDC.get(MDC_KEY)); + } } \ No newline at end of file diff --git a/cli/pom.xml b/cli/pom.xml index cc1d2708..2e7bbe51 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3-WEB-17718-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index 6a1b0119..66ec83a9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3-WEB-17718-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index c1f3d11b..07ca18bc 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -16,11 +16,13 @@ import java.time.temporal.ChronoUnit; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.MDC; /** * {@link Consumer} is a component responsible for consuming messages from the bus. @@ -109,14 +111,19 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern return; } - if (isMessageExpired(message)) { - LOG.warn("{} Expired message: {}", loggingTag, jsonMessage); - acknowledgeHandler.autoReject(); - return; - } - ConsumedMessagesAwareMessageHandler consumedMessagesAwareMessageHandler = null; + try { + if(msbConfig.isMdcLoggingEnabled()) { + saveMdc(message); + } + + if (isMessageExpired(message)) { + LOG.warn("{} Expired message: {}", loggingTag, jsonMessage); + acknowledgeHandler.autoReject(); + return; + } + Optional optionalMessageHandler = messageHandlerResolver.resolveMessageHandler(message); if(optionalMessageHandler.isPresent()) { MessageHandler messageHandler = optionalMessageHandler.get(); @@ -135,6 +142,10 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern if(consumedMessagesAwareMessageHandler != null) { consumedMessagesAwareMessageHandler.notifyConsumedMessageIsLost(); } + } finally { + if(msbConfig.isMdcLoggingEnabled()) { + clearMdc(); + } } } @@ -161,4 +172,14 @@ private boolean isMessageExpired(Message message) { return expiryTime.isBefore(now); } + + private void saveMdc(Message message) { + String tags = StringUtils.join(message.getTags(), ","); + MDC.put(msbConfig.getMdcLoggingKeyMessageTags(), tags); + MDC.put(msbConfig.getMdcLoggingKeyCorrelationId(), message.getCorrelationId()); + } + + private void clearMdc() { + MDC.clear(); + } } \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java index 678641d6..3da0432d 100644 --- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java +++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java @@ -8,9 +8,7 @@ import java.io.IOException; -import static io.github.tcdl.msb.config.ConfigurationUtil.getBoolean; -import static io.github.tcdl.msb.config.ConfigurationUtil.getInt; -import static io.github.tcdl.msb.config.ConfigurationUtil.getString; +import static io.github.tcdl.msb.config.ConfigurationUtil.*; /** * {@link MsbConfig} class provides access to configuration properties. @@ -20,18 +18,24 @@ public class MsbConfig { public final Logger LOG = LoggerFactory.getLogger(getClass()); //Broker Adapter Factory class. Represented with brokerAdapterFactory property from config - private String brokerAdapterFactoryClass; + private final String brokerAdapterFactoryClass; //Broker specific configuration. - private Config brokerConfig; + private final Config brokerConfig; private final ServiceDetails serviceDetails; - private String schema; + private final String schema; - private boolean validateMessage; + private final boolean validateMessage; - private int timerThreadPoolSize; + private final int timerThreadPoolSize; + + private final boolean mdcLoggingEnabled; + + private final String mdcLoggingKeyMessageTags; + + private final String mdcLoggingKeyCorrelationId; public MsbConfig(Config loadedConfig) { Config config = loadedConfig.getConfig("msbConfig"); @@ -43,7 +47,9 @@ public MsbConfig(Config loadedConfig) { this.brokerConfig = config.hasPath("brokerConfig") ? config.getConfig("brokerConfig") : ConfigFactory.empty(); this.timerThreadPoolSize = getInt(config, "timerThreadPoolSize"); this.validateMessage = getBoolean(config, "validateMessage"); - + this.mdcLoggingEnabled = getOptionalBoolean(config, "mdcLoggingEnabled").orElse(true); + this.mdcLoggingKeyMessageTags = getOptionalString(config, "mdcLoggingKeyMessageTags").orElse("msbTags"); + this.mdcLoggingKeyCorrelationId = getOptionalString(config, "mdcLoggingKeyCorrelationId").orElse("msbCorrelationId"); LOG.debug("Loaded {}", this); } @@ -84,9 +90,29 @@ public int getTimerThreadPoolSize() { return timerThreadPoolSize; } - @Override - public String toString() { - return String.format("MsbConfig [serviceDetails=%s, schema=%s, validateMessage=%b, timerThreadPoolSize=%d, brokerAdapterFactory=%s, brokerConfig=%s]", serviceDetails, schema, validateMessage, timerThreadPoolSize, brokerAdapterFactoryClass, brokerConfig.root().render()); + public boolean isMdcLoggingEnabled() { + return mdcLoggingEnabled; } + public String getMdcLoggingKeyMessageTags() { + return mdcLoggingKeyMessageTags; + } + + public String getMdcLoggingKeyCorrelationId() { + return mdcLoggingKeyCorrelationId; + } + + @Override public String toString() { + return "MsbConfig{" + + "brokerAdapterFactoryClass='" + brokerAdapterFactoryClass + '\'' + + ", brokerConfig=" + brokerConfig + + ", serviceDetails=" + serviceDetails + + ", schema='" + schema + '\'' + + ", validateMessage=" + validateMessage + + ", timerThreadPoolSize=" + timerThreadPoolSize + + ", mdcLoggingEnabled=" + mdcLoggingEnabled + + ", mdcLoggingKeyMessageTags='" + mdcLoggingKeyMessageTags + '\'' + + ", mdcLoggingKeyCorrelationId='" + mdcLoggingKeyCorrelationId + '\'' + + '}'; + } } diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml index c9f72fc1..200166fb 100644 --- a/core/src/test/resources/logback-test.xml +++ b/core/src/test/resources/logback-test.xml @@ -3,7 +3,7 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n + %d{HH:mm:ss.SSS} %-5level %logger [%t] - %m%n diff --git a/examples/pom.xml b/examples/pom.xml index c25fe248..40ad57f8 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3-WEB-17718-SNAPSHOT ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index ac5c45c2..e5e77b31 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3-WEB-17718-SNAPSHOT msb java msb java pom From 5c7b4909fe57b4c2891e523223a02f7e800b24b8 Mon Sep 17 00:00:00 2001 From: anha1 Date: Wed, 27 Jan 2016 16:24:18 +0200 Subject: [PATCH 065/226] WEB-17718 MDC logging --- acceptance/pom.xml | 2 +- amqp/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- .../java/io/github/tcdl/msb/Consumer.java | 4 +- .../io/github/tcdl/msb/config/MsbConfig.java | 10 ++-- .../java/io/github/tcdl/msb/ConsumerTest.java | 46 +++++++++++++++++++ .../io/github/tcdl/msb/support/TestUtils.java | 6 ++- doc/MSB.md | 6 +++ examples/pom.xml | 2 +- pom.xml | 2 +- 11 files changed, 70 insertions(+), 14 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index dfd89458..5efbeaab 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-WEB-17718-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/amqp/pom.xml b/amqp/pom.xml index 9df7f85b..e5a9524a 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-WEB-17718-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/cli/pom.xml b/cli/pom.xml index 2e7bbe51..cc1d2708 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-WEB-17718-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index 66ec83a9..6a1b0119 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-WEB-17718-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index 07ca18bc..0a3ead44 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -114,7 +114,7 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern ConsumedMessagesAwareMessageHandler consumedMessagesAwareMessageHandler = null; try { - if(msbConfig.isMdcLoggingEnabled()) { + if(msbConfig.isMdcLogging()) { saveMdc(message); } @@ -143,7 +143,7 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern consumedMessagesAwareMessageHandler.notifyConsumedMessageIsLost(); } } finally { - if(msbConfig.isMdcLoggingEnabled()) { + if(msbConfig.isMdcLogging()) { clearMdc(); } } diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java index 3da0432d..b60c08a3 100644 --- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java +++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java @@ -31,7 +31,7 @@ public class MsbConfig { private final int timerThreadPoolSize; - private final boolean mdcLoggingEnabled; + private final boolean mdcLogging; private final String mdcLoggingKeyMessageTags; @@ -47,7 +47,7 @@ public MsbConfig(Config loadedConfig) { this.brokerConfig = config.hasPath("brokerConfig") ? config.getConfig("brokerConfig") : ConfigFactory.empty(); this.timerThreadPoolSize = getInt(config, "timerThreadPoolSize"); this.validateMessage = getBoolean(config, "validateMessage"); - this.mdcLoggingEnabled = getOptionalBoolean(config, "mdcLoggingEnabled").orElse(true); + this.mdcLogging = getOptionalBoolean(config, "mdcLogging").orElse(true); this.mdcLoggingKeyMessageTags = getOptionalString(config, "mdcLoggingKeyMessageTags").orElse("msbTags"); this.mdcLoggingKeyCorrelationId = getOptionalString(config, "mdcLoggingKeyCorrelationId").orElse("msbCorrelationId"); LOG.debug("Loaded {}", this); @@ -90,8 +90,8 @@ public int getTimerThreadPoolSize() { return timerThreadPoolSize; } - public boolean isMdcLoggingEnabled() { - return mdcLoggingEnabled; + public boolean isMdcLogging() { + return mdcLogging; } public String getMdcLoggingKeyMessageTags() { @@ -110,7 +110,7 @@ public String getMdcLoggingKeyCorrelationId() { ", schema='" + schema + '\'' + ", validateMessage=" + validateMessage + ", timerThreadPoolSize=" + timerThreadPoolSize + - ", mdcLoggingEnabled=" + mdcLoggingEnabled + + ", mdcLogging=" + mdcLogging + ", mdcLoggingKeyMessageTags='" + mdcLoggingKeyMessageTags + '\'' + ", mdcLoggingKeyCorrelationId='" + mdcLoggingKeyCorrelationId + '\'' + '}'; diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index c373e86e..274b6bc1 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -1,5 +1,7 @@ package io.github.tcdl.msb; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; @@ -20,8 +22,10 @@ import java.time.Clock; import java.time.Instant; import java.time.ZoneId; +import java.util.Map; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,12 +34,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.typesafe.config.ConfigFactory; +import org.slf4j.MDC; @RunWith(MockitoJUnitRunner.class) public class ConsumerTest { private static final String TOPIC = "test:consumer"; + private static final String MDC_KEY_TAGS = "msbTags"; + + private static final String MDC_KEY_CORR_ID = "msbCorrelationId"; + + private static final String CORRELATION_ID = "34223432423423"; + @Mock private ConsumerAdapter adapterMock; @@ -76,6 +87,10 @@ public void setUp() { when(consumedMessagesAwareMessageHandlerResolverMock.resolveMessageHandler(any())) .thenReturn(Optional.of(consumedMessagesAwareMessageHandlerMock)); + + when(msbConfMock.getMdcLoggingKeyCorrelationId()).thenReturn(MDC_KEY_CORR_ID); + when(msbConfMock.getMdcLoggingKeyMessageTags()).thenReturn(MDC_KEY_TAGS); + when(msbConfMock.isMdcLogging()).thenReturn(true); } @Test(expected = NullPointerException.class) @@ -249,6 +264,37 @@ public void testHandleRawMessageConsumeFromTopicExpiredMessage() throws JsonConv verifyMessageNotHandled(); } + @Test + public void testSaveMdcSuccess() throws JsonConversionException { + verifyMdc(true); + } + + @Test + public void testSaveMdcDisabled() throws JsonConversionException { + when(msbConfMock.isMdcLogging()).thenReturn(false); + verifyMdc(false); + } + + private void verifyMdc(boolean isMdcExpected) { + Message originalMessage = TestUtils.createMsbRequestMessage(TOPIC, null, CORRELATION_ID, TestUtils.createSimpleRequestPayload(), "tag1", "tag2", "tag3"); + + MessageHandlerInvokeStrategy testInvokeStrategy = (messageHandler, message, acknowledgeHandler) -> { + if(isMdcExpected) { + assertEquals(MDC.get(MDC_KEY_TAGS), "tag1,tag2,tag3"); + assertEquals(MDC.get(MDC_KEY_CORR_ID), CORRELATION_ID); + } else { + assertTrue(StringUtils.isEmpty(MDC.get(MDC_KEY_TAGS))); + assertTrue(StringUtils.isEmpty(MDC.get(MDC_KEY_CORR_ID))); + } + }; + + Consumer consumer = new Consumer(adapterMock, testInvokeStrategy, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + + consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); + Map map = MDC.getCopyOfContextMap(); + assertTrue("MDC cleanup was expected but was not performed", map == null || map.isEmpty()); + } + private Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) { Instant MOMENT_IN_PAST = Instant.parse("2007-12-03T10:15:30.00Z"); diff --git a/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java b/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java index 930a8ac8..c4fd40b8 100644 --- a/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java +++ b/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java @@ -136,9 +136,13 @@ public static Message createMsbRequestMessage(String topicTo, String payloadStri } public static Message createMsbRequestMessage(String topicTo, String instanceId, RestPayload payload, String... tags) { + return createMsbRequestMessage(topicTo, instanceId, null, payload, tags); + } + + public static Message createMsbRequestMessage(String topicTo, String instanceId, String correlationId, RestPayload payload, String... tags) { ObjectMapper payloadMapper = createMessageMapper(); JsonNode payloadNode = Utils.convert(payload, JsonNode.class, payloadMapper); - return createMsbRequestMessage(topicTo, instanceId, null, payloadNode, tags); + return createMsbRequestMessage(topicTo, instanceId, correlationId, payloadNode, tags); } private static Message createMsbRequestMessage(String topicTo, String instanceId, String correlationId, JsonNode payloadNode, String... tags) { diff --git a/doc/MSB.md b/doc/MSB.md index bbb24cad..373b6fa7 100644 --- a/doc/MSB.md +++ b/doc/MSB.md @@ -313,6 +313,12 @@ Here, the override field `name = ${?MSB_SERVICE_NAME}` simply vanishes if there' `brokerAdapterFactory` – message broker class. Defaults to `"io.github.tcdl.adapters.amqp.AmqpAdapterFactory"`. +`mdcLogging` - automatic Mapped Diagnostic Context logging toggle, true/false. Defaults to true. + +`mdcLoggingKeyMessageTags` - Mapped Diagnostic Context key for message tags. Defaults to `msbTags`. + +`mdcLoggingKeyCorrelationId` - Mapped Diagnostic Context key for message correlationId. Defaults to `msbCorrelationId`. + ### Description of AMQP connection configuration fields The _key values pairs_ described in this section are specific for the chosen Broker. The section `brokerConfig` from [reference.conf](/core/src/main/resources/reference.conf) file override values from [amqp.conf](/amqp/src/main/resources/amqp.conf). diff --git a/examples/pom.xml b/examples/pom.xml index 40ad57f8..c25fe248 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-WEB-17718-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index e5e77b31..ac5c45c2 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.3-WEB-17718-SNAPSHOT + 1.4.3-SNAPSHOT msb java msb java pom From cff16d5750791bc8ac8c2d10844491af7ced0fd6 Mon Sep 17 00:00:00 2001 From: anha1 Date: Thu, 28 Jan 2016 10:28:01 +0200 Subject: [PATCH 066/226] WEB-17718 multiple dynamic tags support --- .../java/io/github/tcdl/msb/api/Requester.java | 8 ++++---- .../io/github/tcdl/msb/impl/RequesterImpl.java | 14 +++++++++----- .../io/github/tcdl/msb/impl/RequesterImplTest.java | 8 +++++--- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/api/Requester.java b/core/src/main/java/io/github/tcdl/msb/api/Requester.java index d64de8ea..32ee940c 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/Requester.java +++ b/core/src/main/java/io/github/tcdl/msb/api/Requester.java @@ -33,11 +33,11 @@ public interface Requester { * In case Requester created with expectation for responses then process them. * * @param requestPayload payload which will be sent to bus - * @param tag to add to the message + * @param tags to add to the message * @throws ChannelException if an error is encountered during publishing to bus * @throws JsonConversionException if unable to parse message to JSON before sending to bus */ - void publish(Object requestPayload, String tag); + void publish(Object requestPayload, String... tags); /** * Wraps a payload with protocol information, preserves original message and sends to bus. @@ -45,11 +45,11 @@ public interface Requester { * * @param requestPayload payload which will be sent to bus * @param originalMessage - * @param tag to add to the message + * @param tags to add to the message * @throws ChannelException if an error is encountered during publishing to bus * @throws JsonConversionException if unable to parse message to JSON before sending to bus */ - void publish(Object requestPayload, Message originalMessage, String tag); + void publish(Object requestPayload, Message originalMessage, String... tags); /** * Wraps a payload with protocol information, preserves original message and sends to bus. diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java index d6312c56..c4cb8307 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java @@ -73,8 +73,8 @@ public void publish(Object requestPayload) { * {@inheritDoc} */ @Override - public void publish(Object requestPayload, String tag) { - publish(requestPayload, null, tag); + public void publish(Object requestPayload, String... tags) { + publish(requestPayload, null, tags); } /** @@ -89,10 +89,14 @@ public void publish(Object requestPayload, Message originalMessage) { * {@inheritDoc} */ @Override - public void publish(Object requestPayload, Message originalMessage, String tag) { + public void publish(Object requestPayload, Message originalMessage, String... tags) { MessageTemplate messageTemplate = MessageTemplate.copyOf(requestOptions.getMessageTemplate()); - if (tag != null) { - messageTemplate.addTag(tag); + if (tags != null) { + for(String tag: tags) { + if(tag != null) { + messageTemplate.addTag(tag); + } + } } Message.Builder messageBuilder = messageFactory.createRequestMessageBuilder(namespace, messageTemplate, originalMessage); Message message = messageFactory.createRequestMessage(messageBuilder, requestPayload); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index 3c253020..7afae280 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -206,16 +206,18 @@ public void testRequestMessageWithTags() throws Exception { .build(); String tag = "requester-tag"; - String dynamicTag = "dynamic-tag"; + String dynamicTag1 = "dynamic-tag1"; + String dynamicTag2 = "dynamic-tag2"; + String nullTag = null; RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); RequestOptions requestOptions = TestUtils.createSimpleRequestOptionsWithTags(tag); Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() {}); - requester.publish(requestPayload, dynamicTag); + requester.publish(requestPayload, dynamicTag1, dynamicTag2, nullTag); verify(producerMock).publish(messageArgumentCaptor.capture()); Message requestMessage = messageArgumentCaptor.getValue(); - assertArrayEquals(new String[]{tag, dynamicTag}, requestMessage.getTags().toArray()); + assertArrayEquals(new String[]{tag, dynamicTag1, dynamicTag2}, requestMessage.getTags().toArray()); } private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout, Integer ackTimeout , Callback endHandler) throws Exception { From 99e97f681df0d424886141bfd94c5e85051ecd48 Mon Sep 17 00:00:00 2001 From: anha1 Date: Thu, 28 Jan 2016 13:34:10 +0200 Subject: [PATCH 067/226] WEB-17718 MDC logging --- .../amqp/AmqpAdapterFactoryExecutorTest.java | 42 +++++------------- .../adapters/amqp/AmqpAdapterFactoryTest.java | 17 +++----- .../test/resources/broker_bounded_queue.conf | 7 +++ .../resources/broker_unbounded_queue.conf | 7 +++ .../io/github/tcdl/msb/config/MsbConfig.java | 12 ++++-- core/src/main/resources/reference.conf | 8 ++++ .../adapters/AdapterFactoryLoaderTest.java | 43 +++++++++++-------- core/src/test/resources/reference.conf | 8 ++++ doc/MSB.md | 12 ++++-- 9 files changed, 87 insertions(+), 69 deletions(-) create mode 100644 amqp/src/test/resources/broker_bounded_queue.conf create mode 100644 amqp/src/test/resources/broker_unbounded_queue.conf diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java index 35323ec6..f1b05603 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java @@ -3,8 +3,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.withSettings; +import static org.mockito.Mockito.*; + import io.github.tcdl.msb.config.MsbConfig; import java.util.concurrent.ArrayBlockingQueue; @@ -12,28 +12,21 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; - import org.junit.Test; - import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.Recoverable; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +@RunWith(MockitoJUnitRunner.class) public class AmqpAdapterFactoryExecutorTest { + private static final Config CONFIG = ConfigFactory.load("reference.conf"); - String basicConfig = "msbConfig {" - + " timerThreadPoolSize = 1\n" - + " validateMessage = true\n" - + " brokerAdapterFactory = \"AmqpAdapterFactory\" \n" - + " serviceDetails = {" - + " name = \"test_msb\" \n" - + " version = \"1.0.1\" \n" - + " instanceId = \"msbd06a-ed59-4a39-9f95-811c5fb6ab87\" \n" - + " } \n" - + " %s" - + "}"; + private static final Config CONFIG_BOUNDED = CONFIG.withFallback(ConfigFactory.load("broker_bounded_queue.conf")); + private static final Config CONFIG_UNBOUNDED = CONFIG.withFallback(ConfigFactory.load( "broker_unbounded_queue.conf")); private static class MockAdapterFactory extends AmqpAdapterFactory { @Override @@ -44,15 +37,7 @@ protected Connection createConnection(ConnectionFactory connectionFactory) { @Test public void testCreateConsumerThreadPoolBoundedQueue() { - String brokerConf = - " brokerConfig = { " - + " charsetName = \"UTF-8\"\n" - + " consumerThreadPoolSize = 5\n" - + " consumerThreadPoolQueueCapacity = 20\n" - + " }"; - - Config msbConfig = ConfigFactory.parseString(String.format(basicConfig, brokerConf)); - MsbConfig msbConfigurations = new MsbConfig(msbConfig); + MsbConfig msbConfigurations = new MsbConfig(CONFIG_BOUNDED); AmqpAdapterFactory adapterFactory = new MockAdapterFactory(); adapterFactory.init(msbConfigurations); @@ -70,14 +55,7 @@ public void testCreateConsumerThreadPoolBoundedQueue() { @Test public void testCreateConsumerThreadPoolUnboundedQueue() { - String brokerConf = - " brokerConfig = { " - + " charsetName = \"UTF-8\"\n" - + " consumerThreadPoolSize = 5\n" - + " consumerThreadPoolQueueCapacity = -1\n" - + " }"; - Config msbConfig = ConfigFactory.parseString(String.format(basicConfig, brokerConf)); - MsbConfig msbConfigurations = new MsbConfig(msbConfig); + MsbConfig msbConfigurations = new MsbConfig(CONFIG_UNBOUNDED); AmqpAdapterFactory adapterFactory = new MockAdapterFactory(); adapterFactory.init(msbConfigurations); diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java index 536c6f07..c1655802 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java @@ -33,6 +33,9 @@ @RunWith(MockitoJUnitRunner.class) public class AmqpAdapterFactoryTest { + + private static final Config CONFIG = ConfigFactory.load("reference.conf"); + final Charset charset = Charset.forName("UTF-8"); final String host = "127.0.0.1"; final int port = 5672; @@ -66,18 +69,8 @@ public class AmqpAdapterFactoryTest { @Before public void setUp() { - String configStr = "msbConfig {" - + " timerThreadPoolSize = 1\n" - + " brokerAdapterFactory = \"AmqpAdapterFactory\" \n" - + " validateMessage = true\n" - + " serviceDetails = {" - + " name = \"test_msb\" \n" - + " version = \"1.0.1\" \n" - + " instanceId = \"msbd06a-ed59-4a39-9f95-811c5fb6ab87\" \n" - + " } \n" - + "}"; - Config msbConfig = ConfigFactory.parseString(configStr); - msbConfigurations = new MsbConfig(msbConfig); + + msbConfigurations = new MsbConfig(CONFIG); //Define conditions for ExecutorService termination try { diff --git a/amqp/src/test/resources/broker_bounded_queue.conf b/amqp/src/test/resources/broker_bounded_queue.conf new file mode 100644 index 00000000..66770e46 --- /dev/null +++ b/amqp/src/test/resources/broker_bounded_queue.conf @@ -0,0 +1,7 @@ +msbConfig { + brokerConfig = { + charsetName = "UTF-8" + consumerThreadPoolSize = 5 + consumerThreadPoolQueueCapacity = 20 + } +} \ No newline at end of file diff --git a/amqp/src/test/resources/broker_unbounded_queue.conf b/amqp/src/test/resources/broker_unbounded_queue.conf new file mode 100644 index 00000000..b7febdbb --- /dev/null +++ b/amqp/src/test/resources/broker_unbounded_queue.conf @@ -0,0 +1,7 @@ +msbConfig { + brokerConfig = { + charsetName = "UTF-8" + consumerThreadPoolSize = 5 + consumerThreadPoolQueueCapacity = -1 + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java index b60c08a3..c297af3f 100644 --- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java +++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java @@ -47,9 +47,13 @@ public MsbConfig(Config loadedConfig) { this.brokerConfig = config.hasPath("brokerConfig") ? config.getConfig("brokerConfig") : ConfigFactory.empty(); this.timerThreadPoolSize = getInt(config, "timerThreadPoolSize"); this.validateMessage = getBoolean(config, "validateMessage"); - this.mdcLogging = getOptionalBoolean(config, "mdcLogging").orElse(true); - this.mdcLoggingKeyMessageTags = getOptionalString(config, "mdcLoggingKeyMessageTags").orElse("msbTags"); - this.mdcLoggingKeyCorrelationId = getOptionalString(config, "mdcLoggingKeyCorrelationId").orElse("msbCorrelationId"); + + Config mdcLogging = config.getConfig("mdcLogging"); + Config mdcLoggingMessageKeys= mdcLogging.getConfig("messageKeys"); + + this.mdcLogging = getBoolean(mdcLogging, "enabled"); + this.mdcLoggingKeyMessageTags = getString(mdcLoggingMessageKeys, "messageTags"); + this.mdcLoggingKeyCorrelationId = getString(mdcLoggingMessageKeys, "correlationId"); LOG.debug("Loaded {}", this); } @@ -103,6 +107,7 @@ public String getMdcLoggingKeyCorrelationId() { } @Override public String toString() { + //please keep custom "brokerConfig" data return "MsbConfig{" + "brokerAdapterFactoryClass='" + brokerAdapterFactoryClass + '\'' + ", brokerConfig=" + brokerConfig + @@ -113,6 +118,7 @@ public String getMdcLoggingKeyCorrelationId() { ", mdcLogging=" + mdcLogging + ", mdcLoggingKeyMessageTags='" + mdcLoggingKeyMessageTags + '\'' + ", mdcLoggingKeyCorrelationId='" + mdcLoggingKeyCorrelationId + '\'' + + ", brokerConfig='" + brokerConfig.root().render() + '\'' + '}'; } } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index b41db30e..f5b6ada7 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -20,5 +20,13 @@ msbConfig { brokerConfig = { } + # Mapped Diagnostic Context logging settings + mdcLogging = { + enabled = true + messageKeys = { # Mapped Diagnostic Context keys + messageTags = "msbTags" + correlationId = "msbCorrelationId" + } + } } diff --git a/core/src/test/java/io/github/tcdl/msb/adapters/AdapterFactoryLoaderTest.java b/core/src/test/java/io/github/tcdl/msb/adapters/AdapterFactoryLoaderTest.java index 81d17bc2..761dfe9a 100644 --- a/core/src/test/java/io/github/tcdl/msb/adapters/AdapterFactoryLoaderTest.java +++ b/core/src/test/java/io/github/tcdl/msb/adapters/AdapterFactoryLoaderTest.java @@ -4,25 +4,36 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import io.github.tcdl.msb.adapters.mock.MockAdapterFactory; import io.github.tcdl.msb.config.MsbConfig; +import org.junit.Before; import org.junit.Test; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +@RunWith(MockitoJUnitRunner.class) public class AdapterFactoryLoaderTest { - private String basicConfigWithoutAdapterFactory = "msbConfig { %s timerThreadPoolSize = 1, validateMessage = true, " - + " serviceDetails = {name = \"test_msb\", version = \"1.0.1\", instanceId = \"msbd06a-ed59-4a39-9f95-811c5fb6ab87\"} }"; - + private static final Config CONFIG = ConfigFactory.load("reference.conf"); + + private MsbConfig msbConfigSpy; + + @Before + public void setUp() { + msbConfigSpy = spy(new MsbConfig(CONFIG)); + } + @Test public void testCreatedMockAdapterByFactoryClassName(){ - String configStr = String.format(basicConfigWithoutAdapterFactory, "brokerAdapterFactory = \"io.github.tcdl.msb.adapters.mock.MockAdapterFactory\","); - Config config = ConfigFactory.parseString(configStr); - MsbConfig msbConfig = new MsbConfig(config); - AdapterFactoryLoader loader = new AdapterFactoryLoader(msbConfig); + when(msbConfigSpy.getBrokerAdapterFactory()).thenReturn("io.github.tcdl.msb.adapters.mock.MockAdapterFactory"); + AdapterFactoryLoader loader = new AdapterFactoryLoader(msbConfigSpy); AdapterFactory adapterFactory = loader.getAdapterFactory(); assertThat(adapterFactory, instanceOf(MockAdapterFactory.class)); } @@ -31,10 +42,8 @@ public void testCreatedMockAdapterByFactoryClassName(){ public void testThrowExceptionByNonexistentFactoryClassName(){ //Define Nonexistent AdapterFactory class name String nonexistentAdapterFactoryClassName = "io.github.tcdl.msb.adapters.NonexistentAdapterFactory"; - String configStr = String.format(basicConfigWithoutAdapterFactory, "brokerAdapterFactory = \"" + nonexistentAdapterFactoryClassName + "\", "); - Config config = ConfigFactory.parseString(configStr); - MsbConfig msbConfig = new MsbConfig(config); - AdapterFactoryLoader loader = new AdapterFactoryLoader(msbConfig); + when(msbConfigSpy.getBrokerAdapterFactory()).thenReturn(nonexistentAdapterFactoryClassName); + AdapterFactoryLoader loader = new AdapterFactoryLoader(msbConfigSpy); try { loader.getAdapterFactory(); fail("Created an AdapterFactory by nonexistent class!"); @@ -48,10 +57,8 @@ public void testThrowExceptionByNonexistentFactoryClassName(){ public void testThrowExceptionByIncorrectAdapterFactoryConstructor(){ //Define AdapterFactory class name with a class without default constructor String adapterFactoryClassNameWithoutDefaultConstructor = "java.lang.Integer"; - String configStr = String.format(basicConfigWithoutAdapterFactory, "brokerAdapterFactory = \"" + adapterFactoryClassNameWithoutDefaultConstructor + "\", "); - Config config = ConfigFactory.parseString(configStr); - MsbConfig msbConfig = new MsbConfig(config); - AdapterFactoryLoader loader = new AdapterFactoryLoader(msbConfig); + when(msbConfigSpy.getBrokerAdapterFactory()).thenReturn(adapterFactoryClassNameWithoutDefaultConstructor); + AdapterFactoryLoader loader = new AdapterFactoryLoader(msbConfigSpy); try { loader.getAdapterFactory(); fail("Created an AdapterFactory by class without default constructor!"); @@ -65,10 +72,8 @@ public void testThrowExceptionByIncorrectAdapterFactoryConstructor(){ public void testThrowExceptionByIncorrectAdapterFactoryInterfaceImplementation(){ //Define AdapterFactory class name with a class that doesn't implement AdapterFactory interface String incorrectAdapterFactoryImplementationClassName = "java.lang.StringBuilder"; - String configStr = String.format(basicConfigWithoutAdapterFactory, "brokerAdapterFactory = \"" + incorrectAdapterFactoryImplementationClassName + "\", "); - Config config = ConfigFactory.parseString(configStr); - MsbConfig msbConfig = new MsbConfig(config); - AdapterFactoryLoader loader = new AdapterFactoryLoader(msbConfig); + when(msbConfigSpy.getBrokerAdapterFactory()).thenReturn(incorrectAdapterFactoryImplementationClassName); + AdapterFactoryLoader loader = new AdapterFactoryLoader(msbConfigSpy); try { loader.getAdapterFactory(); fail("Created an AdapterFactory by class that doesn't implement AdapterFactory interface!"); diff --git a/core/src/test/resources/reference.conf b/core/src/test/resources/reference.conf index efc69b31..c7616dae 100644 --- a/core/src/test/resources/reference.conf +++ b/core/src/test/resources/reference.conf @@ -19,5 +19,13 @@ msbConfig { brokerConfig = { } + # Mapped Diagnostic Context logging settings + mdcLogging = { + enabled = true + messageKeys = { # Mapped Diagnostic Context keys + messageTags = "msbTags" + correlationId = "msbCorrelationId" + } + } } diff --git a/doc/MSB.md b/doc/MSB.md index 373b6fa7..02414067 100644 --- a/doc/MSB.md +++ b/doc/MSB.md @@ -313,11 +313,17 @@ Here, the override field `name = ${?MSB_SERVICE_NAME}` simply vanishes if there' `brokerAdapterFactory` – message broker class. Defaults to `"io.github.tcdl.adapters.amqp.AmqpAdapterFactory"`. -`mdcLogging` - automatic Mapped Diagnostic Context logging toggle, true/false. Defaults to true. +### Mapped Diagnostic Context settings +This section provides settings for Mapped Diagnostic Context logging that gives a possibility to save some parameters of the incoming messages into a thread-local storage so it would be easier to track message processing. +The section `mdcLogging`: -`mdcLoggingKeyMessageTags` - Mapped Diagnostic Context key for message tags. Defaults to `msbTags`. +`enabled` - automatic Mapped Diagnostic Context logging toggle, true/false. Defaults to true. -`mdcLoggingKeyCorrelationId` - Mapped Diagnostic Context key for message correlationId. Defaults to `msbCorrelationId`. +The nested section `messageKeys`: + +`messageTags` - Mapped Diagnostic Context key for message tags. Defaults to `msbTags`. + +`correlationId` - Mapped Diagnostic Context key for message correlationId. Defaults to `msbCorrelationId`. ### Description of AMQP connection configuration fields The _key values pairs_ described in this section are specific for the chosen Broker. From c0b049e1bb903c7ad3e91ae2bc6a9dfa6a7056a6 Mon Sep 17 00:00:00 2001 From: anha1 Date: Fri, 29 Jan 2016 11:09:02 +0200 Subject: [PATCH 068/226] WEB-17718 MDC logging --- .../src/test/resources/logback-test.xml | 4 +- .../scenarios/requester_responder.story | 5 ++- amqp/src/main/resources/amqp.conf | 1 + .../java/io/github/tcdl/msb/Consumer.java | 10 +++++ .../io/github/tcdl/msb/config/MsbConfig.java | 8 ++++ core/src/main/resources/reference.conf | 1 + .../java/io/github/tcdl/msb/ConsumerTest.java | 39 +++++++++++++++---- core/src/test/resources/reference.conf | 1 + doc/MSB.md | 2 + 9 files changed, 60 insertions(+), 11 deletions(-) diff --git a/acceptance/src/test/resources/logback-test.xml b/acceptance/src/test/resources/logback-test.xml index 2846e0a5..c523cce5 100644 --- a/acceptance/src/test/resources/logback-test.xml +++ b/acceptance/src/test/resources/logback-test.xml @@ -3,14 +3,14 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %logger [%t] tags[%X{msbTags}] corrId[%X{msbCorrelationId}] - %m%n + %d{HH:mm:ss.SSS} %-5level %logger [%t] tags[%X{msbTags}] corrId[%X{msbCorrelationId}] customTagKey[%X{customTagKey}] - %m%n UTF-8 - %-5level tags[%X{msbTags}] corrId[%X{msbCorrelationId}] %m%n + %-5level tags[%X{msbTags}] corrId[%X{msbCorrelationId}] customTagKey[%X{customTagKey}] %m%n diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story index 8e4916ed..f0573a80 100644 --- a/acceptance/src/test/resources/scenarios/requester_responder.story +++ b/acceptance/src/test/resources/scenarios/requester_responder.story @@ -24,13 +24,14 @@ Scenario: Sends a request to a responder server with a custom tag Given responder server responds with '{"result": "hello jbehave"}' And responder server listens on namespace test:jbehave And requester sends requests to namespace test:jbehave -When requester sends a request with tag 'MY_CUSTOM_TAG' +When requester sends a request with tag 'customTagKey:MY_CUSTOM_TAG' Then requester gets response in 5000 ms And responder requests received count equals 1 And response equals |result| |hello jbehave| -And log contains 'MY_CUSTOM_TAG' +And log contains 'tags[customTagKey:MY_CUSTOM_TAG]' +And log contains 'customTagKey[MY_CUSTOM_TAG]' Scenario: Responder ask to retry a first message delivery so it will be redelivered diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf index 6768e3a4..f3e748be 100644 --- a/amqp/src/main/resources/amqp.conf +++ b/amqp/src/main/resources/amqp.conf @@ -27,3 +27,4 @@ config.amqp = { # Specify the size of the limit of unacknowledged messages on a queue basis prefetchCount = 10 } + diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index 0a3ead44..3b1bbddf 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -41,6 +41,7 @@ public class Consumer { private final JsonValidator validator; private final ObjectMapper messageMapper; private final String loggingTag; + private final boolean isSplitTagsForMdcLogging; /** * @param rawAdapter instance of {@link ConsumerAdapter} that allows to receive messages from message bus @@ -80,6 +81,7 @@ public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvokeStrategy message this.rawAdapter.subscribe(this::handleRawMessage); this.loggingTag = String.format("[Consumer for: '%s' on topic: '%s']", messageHandlerResolver.getLoggingName(), topic); + this.isSplitTagsForMdcLogging = !StringUtils.isEmpty(msbConfig.getMdcLoggingSplitTagsBy()); } /** @@ -177,6 +179,14 @@ private void saveMdc(Message message) { String tags = StringUtils.join(message.getTags(), ","); MDC.put(msbConfig.getMdcLoggingKeyMessageTags(), tags); MDC.put(msbConfig.getMdcLoggingKeyCorrelationId(), message.getCorrelationId()); + if(isSplitTagsForMdcLogging) { + for(String tag: message.getTags()) { + String[] parts = StringUtils.split(tag, msbConfig.getMdcLoggingSplitTagsBy(), 2); + if(parts.length == 2) { + MDC.put(parts[0], parts[1]); + } + } + } } private void clearMdc() { diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java index c297af3f..1fc6f795 100644 --- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java +++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java @@ -37,6 +37,8 @@ public class MsbConfig { private final String mdcLoggingKeyCorrelationId; + private final String mdcLoggingSplitTagsBy; + public MsbConfig(Config loadedConfig) { Config config = loadedConfig.getConfig("msbConfig"); @@ -52,6 +54,7 @@ public MsbConfig(Config loadedConfig) { Config mdcLoggingMessageKeys= mdcLogging.getConfig("messageKeys"); this.mdcLogging = getBoolean(mdcLogging, "enabled"); + this.mdcLoggingSplitTagsBy = getOptionalString(mdcLogging, "splitTagsBy").orElse(null); this.mdcLoggingKeyMessageTags = getString(mdcLoggingMessageKeys, "messageTags"); this.mdcLoggingKeyCorrelationId = getString(mdcLoggingMessageKeys, "correlationId"); LOG.debug("Loaded {}", this); @@ -106,6 +109,10 @@ public String getMdcLoggingKeyCorrelationId() { return mdcLoggingKeyCorrelationId; } + public String getMdcLoggingSplitTagsBy() { + return mdcLoggingSplitTagsBy; + } + @Override public String toString() { //please keep custom "brokerConfig" data return "MsbConfig{" + @@ -118,6 +125,7 @@ public String getMdcLoggingKeyCorrelationId() { ", mdcLogging=" + mdcLogging + ", mdcLoggingKeyMessageTags='" + mdcLoggingKeyMessageTags + '\'' + ", mdcLoggingKeyCorrelationId='" + mdcLoggingKeyCorrelationId + '\'' + + ", mdcLoggingSplitTagsBy='" + mdcLoggingSplitTagsBy + '\'' + ", brokerConfig='" + brokerConfig.root().render() + '\'' + '}'; } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index f5b6ada7..e55faa79 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -23,6 +23,7 @@ msbConfig { # Mapped Diagnostic Context logging settings mdcLogging = { enabled = true + splitTagsBy = ":" messageKeys = { # Mapped Diagnostic Context keys messageTags = "msbTags" correlationId = "msbCorrelationId" diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index 274b6bc1..0e2f8164 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -45,6 +45,10 @@ public class ConsumerTest { private static final String MDC_KEY_CORR_ID = "msbCorrelationId"; + private static final String MDC_SPLIT_KEY = "tagkey"; + + private static final String MDC_SPLIT_BY = ":"; + private static final String CORRELATION_ID = "34223432423423"; @Mock @@ -91,6 +95,7 @@ public void setUp() { when(msbConfMock.getMdcLoggingKeyCorrelationId()).thenReturn(MDC_KEY_CORR_ID); when(msbConfMock.getMdcLoggingKeyMessageTags()).thenReturn(MDC_KEY_TAGS); when(msbConfMock.isMdcLogging()).thenReturn(true); + when(msbConfMock.getMdcLoggingSplitTagsBy()).thenReturn(MDC_SPLIT_BY); } @Test(expected = NullPointerException.class) @@ -265,26 +270,46 @@ public void testHandleRawMessageConsumeFromTopicExpiredMessage() throws JsonConv } @Test - public void testSaveMdcSuccess() throws JsonConversionException { - verifyMdc(true); + public void testSaveMdcSuccessWithTagsSplit() throws JsonConversionException { + verifyMdc(true, true); + } + + @Test + public void testSaveMdcSuccessNoTagsSplit() throws JsonConversionException { + when(msbConfMock.getMdcLoggingSplitTagsBy()).thenReturn(""); + verifyMdc(true, false); + + when(msbConfMock.getMdcLoggingSplitTagsBy()).thenReturn(null); + verifyMdc(true, false); } @Test public void testSaveMdcDisabled() throws JsonConversionException { when(msbConfMock.isMdcLogging()).thenReturn(false); - verifyMdc(false); + verifyMdc(false, false); } - private void verifyMdc(boolean isMdcExpected) { - Message originalMessage = TestUtils.createMsbRequestMessage(TOPIC, null, CORRELATION_ID, TestUtils.createSimpleRequestPayload(), "tag1", "tag2", "tag3"); + private void verifyMdc(boolean isMdcExpected, boolean isSplitExpected) { + String splitTagVal = "tag2" + MDC_SPLIT_BY + "tag2!$#.$#$$#&&**"; + String splitTag = MDC_SPLIT_KEY + MDC_SPLIT_BY + splitTagVal; + Message originalMessage = TestUtils.createMsbRequestMessage( + TOPIC, null, CORRELATION_ID, TestUtils.createSimpleRequestPayload(), "tag1", splitTag, "tag3"); MessageHandlerInvokeStrategy testInvokeStrategy = (messageHandler, message, acknowledgeHandler) -> { if(isMdcExpected) { - assertEquals(MDC.get(MDC_KEY_TAGS), "tag1,tag2,tag3"); - assertEquals(MDC.get(MDC_KEY_CORR_ID), CORRELATION_ID); + assertEquals("tag1,"+splitTag+",tag3", MDC.get(MDC_KEY_TAGS)); + assertEquals(CORRELATION_ID, MDC.get(MDC_KEY_CORR_ID)); + } else { assertTrue(StringUtils.isEmpty(MDC.get(MDC_KEY_TAGS))); assertTrue(StringUtils.isEmpty(MDC.get(MDC_KEY_CORR_ID))); + + } + + if(isSplitExpected) { + assertEquals(splitTagVal, MDC.get(MDC_SPLIT_KEY)); + } else { + assertTrue(StringUtils.isEmpty(MDC.get(MDC_SPLIT_KEY))); } }; diff --git a/core/src/test/resources/reference.conf b/core/src/test/resources/reference.conf index c7616dae..30c018b4 100644 --- a/core/src/test/resources/reference.conf +++ b/core/src/test/resources/reference.conf @@ -22,6 +22,7 @@ msbConfig { # Mapped Diagnostic Context logging settings mdcLogging = { enabled = true + splitTagsBy = ":" messageKeys = { # Mapped Diagnostic Context keys messageTags = "msbTags" correlationId = "msbCorrelationId" diff --git a/doc/MSB.md b/doc/MSB.md index 02414067..800520e5 100644 --- a/doc/MSB.md +++ b/doc/MSB.md @@ -319,6 +319,8 @@ The section `mdcLogging`: `enabled` - automatic Mapped Diagnostic Context logging toggle, true/false. Defaults to true. +`splitTagsBy` - if a separator like ":" is provided and is not empty, then for tags like "myTag:myTagValue" the tag value "myTagValue" will be available by a key "myTag" in a Mapped Diagnostic Context. Defaults to ":". + The nested section `messageKeys`: `messageTags` - Mapped Diagnostic Context key for message tags. Defaults to `msbTags`. From 2d1e547a3712a5588364781815019998b3747e73 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 1 Feb 2016 11:59:43 +0200 Subject: [PATCH 069/226] Updated javadoc for AmqpMessageConsumer --- .../tcdl/msb/adapters/amqp/AmqpMessageConsumer.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java index b8db3052..cfb417be 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java @@ -7,7 +7,6 @@ import java.io.IOException; import java.nio.charset.Charset; -import java.util.concurrent.ExecutorService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,15 +17,8 @@ import com.rabbitmq.client.Envelope; /** - * Special consumer that allows to process messages coming from single AMQP channel in parallel. - * - * REASON: - * - * AMQP library is implemented in such way that messages that arrive into a single channel are dispatched in a synchronous loop to the consumers and hence - * will not be processed in parallel. - * - * To address this issue {@link AmqpMessageConsumer} just takes incoming message, wraps it in a task and puts into a thread pool. So the actual - * processing is happening in the separate thread from that thread pool. + * Consumer that converts message body to a String before passing it to handler. + * Also rejects message in case of any exception during its processing to prevent AMQP channel from being closed. */ public class AmqpMessageConsumer extends DefaultConsumer { From 1258bb7519cc855d4e0acac12c056283dba2103e Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 8 Feb 2016 16:42:29 +0200 Subject: [PATCH 070/226] Updated release notes in preparation to 1.4.3 release --- release-notes.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/release-notes.html b/release-notes.html index b912b1dd..ceb06df1 100644 --- a/release-notes.html +++ b/release-notes.html @@ -11,6 +11,14 @@

January 15, 2016

MSB-Java version 1.4.2 contains logging infrastructure changes and dependencies updates.
 
 ------------------------------------------------------------------------------------
+Features of MSB-Java version 1.4.3:
+   - Added logging support: all message tags concatenated into one string can be added to consumers thread
+     MDC (see http://www.slf4j.org/api/org/slf4j/MDC.html) in 'msbTags' field. Message tags that have key-value format
+     (e.g. 'requestId:123456' or 'requestId-123456') can be added to consumers thread MDC as separate properties where
+     part before delimiter is a key and part after delimiter is a value. Delimiter is configurable. MDC is cleared
+     after message handler invocation return;
+   - Requester api now allows to publish message with multiple tags.
+
 Features of MSB-Java version 1.4.2:
    - the library uses only slf4j API for logging
      so log4j dependency is no longer included;

From 7de9557d3b26f5ce91d3eb984a25612a0ab543f5 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Mon, 8 Feb 2016 12:27:50 +0200
Subject: [PATCH 071/226] forward namespace support

---
 .../tcdl/msb/acceptance/MsbTestHelper.java    | 11 ++++---
 .../bdd/steps/RequesterResponderSteps.java    | 18 ++++++++--
 .../scenarios/requester_responder.story       |  9 +++++
 .../github/tcdl/msb/api/RequestOptions.java   | 29 ++++++++++++----
 .../github/tcdl/msb/api/message/Topics.java   | 10 ++++--
 .../github/tcdl/msb/impl/RequesterImpl.java   |  7 +++-
 .../tcdl/msb/message/MessageFactory.java      |  8 ++---
 .../java/io/github/tcdl/msb/ConsumerTest.java |  2 +-
 .../java/io/github/tcdl/msb/ProducerTest.java |  2 +-
 .../tcdl/msb/api/RequestOptionsTest.java      | 10 ++++++
 .../tcdl/msb/impl/RequesterImplTest.java      | 31 ++++++++++++++---
 .../tcdl/msb/message/MessageFactoryTest.java  | 33 ++++++++++++++++---
 .../io/github/tcdl/msb/support/TestUtils.java | 10 +++---
 release-notes.html                            |  7 ++--
 14 files changed, 148 insertions(+), 39 deletions(-)

diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
index 72848e18..fc0472f7 100644
--- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
+++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
@@ -71,20 +71,21 @@ public ObjectMapper getPayloadMapper() {
     }
 
     public  Requester createRequester(String namespace, Integer numberOfResponses, Class responsePayloadClass) {
-        return createRequester(DEFAULT_CONTEXT_NAME, namespace, numberOfResponses, null, null, responsePayloadClass);
+        return createRequester(DEFAULT_CONTEXT_NAME, namespace, null, numberOfResponses, null, null, responsePayloadClass);
     }
 
-    public  Requester createRequester(String contextName, String namespace, Integer numberOfResponses, Class responsePayloadClass) {
-        return createRequester(contextName, namespace, numberOfResponses, null, null, responsePayloadClass);
+    public  Requester createRequester(String contextName, String namespace, String forwardNamespace, Integer numberOfResponses, Class responsePayloadClass) {
+        return createRequester(contextName, namespace, forwardNamespace, numberOfResponses, null, null, responsePayloadClass);
     }
 
     public  Requester createRequester(String namespace, Integer numberOfResponses, Integer ackTimeout, Integer responseTimeout, Class responsePayloadClass) {
-        return createRequester(DEFAULT_CONTEXT_NAME, namespace, numberOfResponses, ackTimeout, responseTimeout, responsePayloadClass);
+        return createRequester(DEFAULT_CONTEXT_NAME, namespace, null, numberOfResponses, ackTimeout, responseTimeout, responsePayloadClass);
     }
 
-    public  Requester createRequester(String contextName, String namespace, Integer numberOfResponses, Integer ackTimeout, Integer responseTimeout, Class responsePayloadClass) {
+    public  Requester createRequester(String contextName, String namespace, String forwardNamespace, Integer numberOfResponses, Integer ackTimeout, Integer responseTimeout, Class responsePayloadClass) {
         RequestOptions options = new RequestOptions.Builder()
                 .withWaitForResponses(numberOfResponses)
+                .withForwardNamespace(forwardNamespace)
                 .withAckTimeout(Utils.ifNull(ackTimeout, 5000))
                 .withResponseTimeout(Utils.ifNull(responseTimeout, 15000))
                 .build();
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 6238b67a..08d5f163 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -41,6 +41,7 @@ public class RequesterResponderSteps extends MsbSteps {
     private volatile int responseProcessingDelay;
     private volatile int responsesToSendCount;
     private volatile int responsesToExpectCount;
+    private volatile String latestForwardNamespace = null;
 
     public Optional getDefaultRequestsAckType() {
         return defaultRequestsAckType;
@@ -82,7 +83,7 @@ public void createResponderServer(String contextName, String namespace) {
                     }
 
                     nextRequestAckType = Optional.empty();
-
+                    latestForwardNamespace = responderContext.getOriginalMessage().getTopics().getForward();
                     if (isSendResponse) {
                         RestPayload payload = new RestPayload.Builder()
                                 .withBody(Utils.fromJson(responseBody, Map.class, mapper))
@@ -152,17 +153,23 @@ public void createRequester(String namespace) {
         createRequester(DEFAULT_CONTEXT_NAME, namespace);
     }
 
+    // requester steps
+    @Given("requester sets forwarding to $forwardNamespace and sends requests to namespace $namespace")
+    public void createRequesterWithForwarding(String forwardNamespace, String namespace) {
+        requester = helper.createRequester(DEFAULT_CONTEXT_NAME, namespace, forwardNamespace, 0, RestPayload.class);
+    }
+
     // requester steps
     @Given("requester (with $requestTimeout ms request timeout to receive $responseCount responses) sends requests to namespace $namespace")
     public void createRequester(int requestTimeout, int responseCount, String namespace) {
         responseCountDown = new CountDownLatch(responseCount);
         responsesToExpectCount = responseCount;
-        requester = helper.createRequester(DEFAULT_CONTEXT_NAME, namespace, responsesToExpectCount, 100, requestTimeout, RestPayload.class);
+        requester = helper.createRequester(DEFAULT_CONTEXT_NAME, namespace, null, responsesToExpectCount, 100, requestTimeout, RestPayload.class);
     }
 
     @Given("requester from $contextName sends requests to namespace $namespace")
     public void createRequester(String contextName, String namespace) {
-        requester = helper.createRequester(contextName, namespace, 1, RestPayload.class);
+        requester = helper.createRequester(contextName, namespace, null, 1, RestPayload.class);
     }
 
     @When("requester sends a request")
@@ -265,6 +272,11 @@ public void responseEquals(ExamplesTable table) throws Exception {
         outcomes.verify();
     }
 
+    @Then("request forward namespace equals $forwardNamespace")
+    public void responseEquals(String forwardNamespace) throws Exception {
+        Assert.assertEquals(forwardNamespace, latestForwardNamespace);
+    }
+
     @Then("responder requests received count equals $expectedRequestsReceivedCount")
     public void requestCountEquals(int expectedCountRequestsReceived) throws Exception {
         Assert.assertEquals(expectedCountRequestsReceived, countRequestsReceived.get());
diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story
index f0573a80..65334e2d 100644
--- a/acceptance/src/test/resources/scenarios/requester_responder.story
+++ b/acceptance/src/test/resources/scenarios/requester_responder.story
@@ -122,4 +122,13 @@ Then requester will get all responses in 5000 ms
 And responder requests received count equals 1
 
 
+Scenario: Message forwarding
+
+Given responder server responds with '{"result": "hello jbehave - forwarding"}'
+And responder server listens on namespace test:jbehave
+And requester sets forwarding to test:jbehave:forwarding and sends requests to namespace test:jbehave
+When requester sends a request
+Then requester gets response in 5000 ms
+And responder requests received count equals 1
+And request forward namespace equals test:jbehave:forwarding
 
diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
index f933144c..a30d7f21 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
@@ -10,13 +10,13 @@ public class RequestOptions {
     /**
      * Min time (in milliseconds) to wait for acknowledgements.
      */
-    private Integer ackTimeout;
+    private final Integer ackTimeout;
 
     /**
      * Max time (in milliseconds) to wait for responses and acknowledgements. Once this timeout is reached we stop waiting for responses even if
      * {@link #waitForResponses} has not been reached. Beware that acks may adjust this timeout.
      */
-    private Integer responseTimeout;
+    private final Integer responseTimeout;
 
     /**
      * Number of responses to wait for. Once this number is reached (and {@link #ackTimeout} passed) we stop waiting for responses even if
@@ -25,15 +25,21 @@ public class RequestOptions {
      * 0 means not to wait for responses at all.
      * -1 means to wait until {@link #responseTimeout} is reached.
      */
-    private Integer waitForResponses;
+    private final Integer waitForResponses;
 
-    private MessageTemplate messageTemplate;
+    /**
+     * A namespace for messages forwarding performed by a consumer.
+     */
+    private final String forwardNamespace;
 
-    private RequestOptions(Integer ackTimeout, Integer responseTimeout, Integer waitForResponses, MessageTemplate messageTemplate) {
+    private final MessageTemplate messageTemplate;
+
+    private RequestOptions(Integer ackTimeout, Integer responseTimeout, Integer waitForResponses, MessageTemplate messageTemplate, String forwardNamespace) {
         this.ackTimeout = ackTimeout;
         this.responseTimeout = responseTimeout;
         this.waitForResponses = waitForResponses;
         this.messageTemplate = messageTemplate;
+        this.forwardNamespace = forwardNamespace;
     }
 
     public Integer getAckTimeout() {
@@ -57,11 +63,16 @@ public MessageTemplate getMessageTemplate() {
         return messageTemplate;
     }
 
+    public String getForwardNamespace() {
+        return forwardNamespace;
+    }
+
     @Override
     public String toString() {
         return "RequestOptions [ackTimeout=" + ackTimeout
                 + ", responseTimeout=" + responseTimeout
                 + ", waitForResponses=" + waitForResponses
+                + ", forwardNamespace=" + forwardNamespace
                 + (messageTemplate != null ? messageTemplate : "")
                 + "]";
     }
@@ -72,6 +83,7 @@ public static class Builder {
         private Integer responseTimeout;
         private Integer waitForResponses;
         private MessageTemplate messageTemplate;
+        private String forwardNamespace;
 
         public Builder withAckTimeout(Integer ackTimeout) {
             this.ackTimeout = ackTimeout;
@@ -93,8 +105,13 @@ public Builder withMessageTemplate(MessageTemplate messageTemplate) {
             return this;
         }
 
+        public Builder withForwardNamespace(String forward) {
+            this.forwardNamespace = forward;
+            return this;
+        }
+
         public RequestOptions build() {
-            return new RequestOptions(ackTimeout, responseTimeout, waitForResponses, messageTemplate);
+            return new RequestOptions(ackTimeout, responseTimeout, waitForResponses, messageTemplate, forwardNamespace);
         }
     }
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java b/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java
index 20495d88..daeee5db 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java
@@ -8,12 +8,14 @@ public final class Topics {
 
     private final String to;
     private final String response;
+    private final String forward;
 
     @JsonCreator
-    public Topics(@JsonProperty("to") String to, @JsonProperty("response") String response) {
+    public Topics(@JsonProperty("to") String to, @JsonProperty("response") String response,  @JsonProperty("forward") String forward) {
         Validate.notNull(to, "the 'to' must not be null");
         this.to = to;
         this.response = response;
+        this.forward = forward;
     }
 
     public String getTo() {
@@ -24,8 +26,12 @@ public String getResponse() {
         return response;
     }
 
+    public String getForward() {
+        return forward;
+    }
+
     @Override
     public String toString() {
-        return "Topics [to=" + to + ", response=" + response + "]";
+        return "Topics [to=" + to + ", response=" + response + ", forward=" + response + "]";
     }
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
index c4cb8307..cc928be1 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
@@ -98,7 +98,12 @@ public void publish(Object requestPayload, Message originalMessage, String... ta
                 }
             }
         }
-        Message.Builder messageBuilder = messageFactory.createRequestMessageBuilder(namespace, messageTemplate, originalMessage);
+        Message.Builder messageBuilder = messageFactory.createRequestMessageBuilder(
+                namespace,
+                requestOptions.getForwardNamespace(),
+                messageTemplate,
+                originalMessage);
+
         Message message = messageFactory.createRequestMessage(messageBuilder, requestPayload);
 
         //use Collector instance to handle expected responses/acks
diff --git a/core/src/main/java/io/github/tcdl/msb/message/MessageFactory.java b/core/src/main/java/io/github/tcdl/msb/message/MessageFactory.java
index 52d91a1c..da0c60bd 100644
--- a/core/src/main/java/io/github/tcdl/msb/message/MessageFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/message/MessageFactory.java
@@ -49,19 +49,19 @@ public Message createBroadcastMessage(Message.Builder messageBuilder, Object pay
         return createRequestMessage(messageBuilder, payload);
     }
 
-    public Message.Builder createRequestMessageBuilder(String namespace, MessageTemplate messageTemplate, Message originalMessage) {
+    public Message.Builder createRequestMessageBuilder(String namespace, String forwardNamespace, MessageTemplate messageTemplate, Message originalMessage) {
         Topics topic = new Topics(namespace, namespace + ":response:" +
-                this.serviceDetails.getInstanceId());
+                this.serviceDetails.getInstanceId(), forwardNamespace);
         return createMessageBuilder(topic, messageTemplate, originalMessage, false);
     }
 
     public Message.Builder createResponseMessageBuilder(MessageTemplate messageTemplate, Message originalMessage) {
-        Topics topic = new Topics(originalMessage.getTopics().getResponse(), null);
+        Topics topic = new Topics(originalMessage.getTopics().getResponse(), null, null);
         return createMessageBuilder(topic, messageTemplate, originalMessage, true);
     }
 
     public Message.Builder createBroadcastMessageBuilder(String namespace, MessageTemplate messageTemplate) {
-        Topics topic = new Topics(namespace, null);
+        Topics topic = new Topics(namespace, null, null);
         return createMessageBuilder(topic, messageTemplate, null, false);
     }
 
diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
index 0e2f8164..e58d5c14 100644
--- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
@@ -326,7 +326,7 @@ private  Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) {
         MsbConfig msbConf = new MsbConfig(ConfigFactory.load());
         Clock clock = Clock.fixed(MOMENT_IN_PAST, ZoneId.systemDefault());
 
-        Topics topic = new Topics(topicTo, topicTo + ":response:" + msbConf.getServiceDetails().getInstanceId());
+        Topics topic = new Topics(topicTo, topicTo + ":response:" + msbConf.getServiceDetails().getInstanceId(), null);
         MetaMessage.Builder metaBuilder = new MetaMessage.Builder(0, clock.instant(), msbConf.getServiceDetails(), clock);
         return new Message.Builder().withCorrelationId(Utils.generateId()).withId(Utils.generateId()).withTopics(topic).withMetaBuilder(metaBuilder)
                 .build();
diff --git a/core/src/test/java/io/github/tcdl/msb/ProducerTest.java b/core/src/test/java/io/github/tcdl/msb/ProducerTest.java
index 32a5a864..f1190017 100644
--- a/core/src/test/java/io/github/tcdl/msb/ProducerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ProducerTest.java
@@ -108,7 +108,7 @@ private  Message createBrokenRequestMessageWithAndTopicTo(String topicTo) {
         Clock clock = Clock.systemDefaultZone();
         ObjectMapper payloadMapper = TestUtils.createMessageMapper();
 
-        Topics topic = new Topics(topicTo, topicTo + ":response:" + msbConf.getServiceDetails().getInstanceId());
+        Topics topic = new Topics(topicTo, topicTo + ":response:" + msbConf.getServiceDetails().getInstanceId(), null);
         Map body = new HashMap<>();
         body.put("body", "{\\\"x\\\" : 3} garbage");
         RestPayload> payload = new RestPayload.Builder>()
diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
index 1641c31c..e3392152 100644
--- a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
@@ -34,4 +34,14 @@ public void testGetWaitForResponsesConfigsPositive() {
         assertEquals(responsesRemaining, requestOptions.getWaitForResponses());
     }
 
+    @Test
+    public void testForwardNamespace() {
+        String forwardNamespace = "test:forward";
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withForwardNamespace(forwardNamespace)
+                .build();
+
+        assertEquals(forwardNamespace, requestOptions.getForwardNamespace());
+    }
+
 }
\ No newline at end of file
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
index 7afae280..6a9651dd 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
@@ -2,10 +2,7 @@
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doReturn;
@@ -220,6 +217,32 @@ public void testRequestMessageWithTags() throws Exception {
         assertArrayEquals(new String[]{tag, dynamicTag1, dynamicTag2}, requestMessage.getTags().toArray());
     }
 
+    @Test
+    public void testRequestMessageWithForward() throws Exception {
+        String forwardNamespace = "test:forward";
+        ChannelManager channelManagerMock = mock(ChannelManager.class);
+        Producer producerMock = mock(Producer.class);
+        when(channelManagerMock.findOrCreateProducer(NAMESPACE)).thenReturn(producerMock);
+        ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+
+        MsbContextImpl msbContext = TestUtils.createMsbContextBuilder()
+                .withChannelManager(channelManagerMock)
+                .withClock(Clock.systemDefaultZone())
+                .build();
+
+        RestPayload requestPayload = TestUtils.createSimpleRequestPayload();
+        RequestOptions requestOptions = new RequestOptions
+                .Builder()
+                .withForwardNamespace(forwardNamespace).build();
+
+        Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() {});
+        requester.publish(requestPayload);
+        verify(producerMock).publish(messageArgumentCaptor.capture());
+
+        Message requestMessage = messageArgumentCaptor.getValue();
+        assertEquals(forwardNamespace, requestMessage.getTopics().getForward());
+    }
+
     private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout,  Integer ackTimeout , Callback endHandler) throws Exception {
 
         MessageTemplate messageTemplateMock = mock(MessageTemplate.class);
diff --git a/core/src/test/java/io/github/tcdl/msb/message/MessageFactoryTest.java b/core/src/test/java/io/github/tcdl/msb/message/MessageFactoryTest.java
index 80401080..a2f912f9 100644
--- a/core/src/test/java/io/github/tcdl/msb/message/MessageFactoryTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/message/MessageFactoryTest.java
@@ -61,6 +61,7 @@ public void testCreateRequestMessageWithPayload() {
 
         TestUtils.assertRawPayloadContainsBodyText(bodyText, message);
         assertNull(message.getAck());
+        assertNull(message.getTopics().getForward());
     }
 
     @Test
@@ -71,6 +72,7 @@ public void testCreateRequestMessageWithoutPayload() {
 
         assertNull(message.getRawPayload());
         assertNull(message.getAck());
+        assertNull(message.getTopics().getForward());
     }
 
     @Test
@@ -88,6 +90,7 @@ public void testCreateResponseMessageWithPayloadAndAck() {
 
         TestUtils.assertRawPayloadContainsBodyText(bodyText, message);
         assertEquals(ack, message.getAck());
+        assertNull(message.getTopics().getForward());
     }
 
     @Test
@@ -98,6 +101,7 @@ public void testCreateResponseMessageWithoutPayloadAndAck() {
 
         assertNull(message.getRawPayload());
         assertNull(message.getAck());
+        assertNull(message.getTopics().getForward());
     }
 
     @Test
@@ -110,18 +114,35 @@ public void testBroadcastMessage() {
 
         TestUtils.assertRawPayloadContainsBodyText(bodyText, message);
         assertNull(message.getAck());
+        assertNull(message.getTopics().getForward());
     }
 
     @Test
     public void testCreateRequestMessageBuilder() {
         String namespace = "test:request-builder";
 
-        Builder requestMessageBuilder = messageFactory.createRequestMessageBuilder(namespace, messageOptions, null);
+        Builder requestMessageBuilder = messageFactory.createRequestMessageBuilder(namespace, null, messageOptions, null);
         Message message = requestMessageBuilder.build();
 
         assertNotNull(message.getCorrelationId());
         assertThat(message.getTopics().getTo(), is(namespace));
         assertThat(message.getTopics().getResponse(), notNullValue());
+        assertNull(message.getTopics().getForward());
+    }
+
+    @Test
+    public void testCreateRequestMessageBuilderWithForward() {
+        String namespace = "test:request-builder";
+
+        String forwardNamespace = "test:forward";
+
+        Builder requestMessageBuilder = messageFactory.createRequestMessageBuilder(namespace, forwardNamespace, messageOptions, null);
+        Message message = requestMessageBuilder.build();
+
+        assertNotNull(message.getCorrelationId());
+        assertThat(message.getTopics().getTo(), is(namespace));
+        assertThat(message.getTopics().getResponse(), notNullValue());
+        assertThat(message.getTopics().getForward(), is(forwardNamespace));
     }
 
     @Test
@@ -130,7 +151,7 @@ public void testCreateRequestMessageBuilderWithTags() {
         String[] tags = new String[] {"tag1", "tag2"};
         MessageTemplate messageTemplate = TestUtils.createSimpleMessageTemplate(tags);
 
-        Builder requestMessageBuilder = messageFactory.createRequestMessageBuilder(namespace, messageTemplate, null);
+        Builder requestMessageBuilder = messageFactory.createRequestMessageBuilder(namespace, null, messageTemplate, null);
         Message message = requestMessageBuilder.build();
 
         assertArrayEquals(tags, message.getTags().toArray());
@@ -143,7 +164,7 @@ public void testCreateRequestMessageBuilderWithUniqueTags() {
 
         MessageTemplate messageTemplate = TestUtils.createSimpleMessageTemplate(tags);
 
-        Builder requestMessageBuilder = messageFactory.createRequestMessageBuilder(namespace, messageTemplate, null);
+        Builder requestMessageBuilder = messageFactory.createRequestMessageBuilder(namespace, null, messageTemplate, null);
         Message message = requestMessageBuilder.build();
 
         String[] uniqueTags = Stream.of(tags).distinct().collect(Collectors.toList()).toArray(new String[] {});
@@ -155,12 +176,13 @@ public void testCreateRequestMessageBuilderFromOriginalMessage() {
         String namespace = "test:request-builder";
         Message originalMessage = TestUtils.createMsbRequestMessageNoPayload(namespace);
 
-        Builder requestMessageBuilder = messageFactory.createRequestMessageBuilder(namespace, messageOptions, originalMessage);
+        Builder requestMessageBuilder = messageFactory.createRequestMessageBuilder(namespace, null, messageOptions, originalMessage);
         Message message = requestMessageBuilder.build();
 
         assertNotEquals(originalMessage.getCorrelationId(), message.getCorrelationId());
         assertThat(message.getTopics().getTo(), is(namespace));
         assertThat(message.getTopics().getResponse(), notNullValue());
+        assertNull(message.getTopics().getForward());
     }
 
     @Test
@@ -176,6 +198,7 @@ public void testCreateResponseMessageBuilder() {
         assertThat(message.getTopics().getTo(), not(originalMessage.getTopics().getTo()));
         assertThat(message.getTopics().getTo(), is(originalMessage.getTopics().getResponse()));
         assertThat(message.getTopics().getResponse(), nullValue());
+        assertNull(message.getTopics().getForward());
     }
 
     @Test
@@ -189,6 +212,7 @@ public void testCreateResponseMessageBuilderWithTags() {
         Message message = requestMessageBuilder.build();
 
         assertArrayEquals(tags, message.getTags().toArray());
+        assertNull(message.getTopics().getForward());
     }
 
     @Test
@@ -215,6 +239,7 @@ public void testBroadcastMessageBuilder() {
         assertNotNull(message.getCorrelationId());
         assertEquals(topic, message.getTopics().getTo());
         assertThat(message.getTopics().getResponse(), nullValue());
+        assertNull(message.getTopics().getForward());
     }
 
     @Test
diff --git a/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java b/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java
index c4fd40b8..8eda5932 100644
--- a/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java
+++ b/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java
@@ -97,7 +97,7 @@ public static Message createMsbRequestMessageNoPayload(String namespace, String
         MsbConfig msbConf = createMsbConfigurations();
         Clock clock = Clock.systemDefaultZone();
 
-        Topics topic = new Topics(namespace, replyTopic);
+        Topics topic = new Topics(namespace, replyTopic, null);
 
         MetaMessage.Builder metaBuilder = createSimpleMetaBuilder(msbConf, clock);
         return new Message.Builder()
@@ -149,7 +149,7 @@ private static Message createMsbRequestMessage(String topicTo, String instanceId
         MsbConfig msbConf = createMsbConfigurations(instanceId);
         Clock clock = Clock.systemDefaultZone();
 
-        Topics topic = new Topics(topicTo, topicTo + ":response:" + msbConf.getServiceDetails().getInstanceId());
+        Topics topic = new Topics(topicTo, topicTo + ":response:" + msbConf.getServiceDetails().getInstanceId(), null);
         MetaMessage.Builder metaBuilder = createSimpleMetaBuilder(msbConf, clock);
 
         Message.Builder builder = new Message.Builder()
@@ -181,7 +181,7 @@ public static Message createMsbResponseMessageWithAckNoPayload(Acknowledge ack,
         MsbConfig msbConf = createMsbConfigurations();
         Clock clock = Clock.systemDefaultZone();
 
-        Topics topic = new Topics(topicTo, null);
+        Topics topic = new Topics(topicTo, null, null);
         MetaMessage.Builder metaBuilder = createSimpleMetaBuilder(msbConf, clock);
         return new Message.Builder()
                 .withCorrelationId(Utils.ifNull(correlationId, Utils.generateId()))
@@ -197,7 +197,7 @@ public static Message createMsbResponseMessage(Acknowledge ack, JsonNode payload
         MsbConfig msbConf = createMsbConfigurations();
         Clock clock = Clock.systemDefaultZone();
 
-        Topics topic = new Topics(topicTo, null);
+        Topics topic = new Topics(topicTo, null, null);
         MetaMessage.Builder metaBuilder = createSimpleMetaBuilder(msbConf, clock);
         return new Message.Builder()
                 .withCorrelationId(Utils.ifNull(correlationId, Utils.generateId()))
@@ -212,7 +212,7 @@ public static Message createMsbResponseMessage(Acknowledge ack, JsonNode payload
     public static Message.Builder createMessageBuilder(Clock clock) {
         MsbConfig msbConf = createMsbConfigurations();
 
-        Topics topic = new Topics("", "");
+        Topics topic = new Topics("", "", null);
         MetaMessage.Builder metaBuilder = createSimpleMetaBuilder(msbConf, clock);
         return new Message.Builder()
                 .withCorrelationId(Utils.generateId())
diff --git a/release-notes.html b/release-notes.html
index ceb06df1..d8e7897e 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -4,11 +4,11 @@
 
 
 
-

Welcome to MSB-Java version 1.4.2

+

Welcome to MSB-Java version 1.4.3

-

January 15, 2016

+

February 08, 2016

-
MSB-Java version 1.4.2 contains logging infrastructure changes and dependencies updates.
+
MSB-Java version 1.4.3 minor improvements.
 
 ------------------------------------------------------------------------------------
 Features of MSB-Java version 1.4.3:
@@ -17,6 +17,7 @@ 

January 15, 2016

(e.g. 'requestId:123456' or 'requestId-123456') can be added to consumers thread MDC as separate properties where part before delimiter is a key and part after delimiter is a value. Delimiter is configurable. MDC is cleared after message handler invocation return; + - it is possible to define a messages forwarding namespace for requests; - Requester api now allows to publish message with multiple tags. Features of MSB-Java version 1.4.2: From abb037957f01b02c4b0cc9a0ca3d85855a0c5830 Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 8 Feb 2016 12:44:44 +0200 Subject: [PATCH 072/226] forward namespace support --- release-notes.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes.html b/release-notes.html index d8e7897e..0be3eeed 100644 --- a/release-notes.html +++ b/release-notes.html @@ -8,7 +8,7 @@

Welcome to MSB-Java version 1.4.3

February 08, 2016

-
MSB-Java version 1.4.3 minor improvements.
+
MSB-Java version 1.4.3 contains minor improvements.
 
 ------------------------------------------------------------------------------------
 Features of MSB-Java version 1.4.3:

From fafb1dc1ff16cfe8251b9968b5bf3cb0a91ab405 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Tue, 9 Feb 2016 09:55:30 +0200
Subject: [PATCH 073/226] minor documentation fixes

---
 doc/MSB.md         | 1 +
 release-notes.html | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/doc/MSB.md b/doc/MSB.md
index 800520e5..79040c9c 100644
--- a/doc/MSB.md
+++ b/doc/MSB.md
@@ -99,6 +99,7 @@ correlationId           | unique id of message sequence related to single conver
 topics                  | section for routing information
   to                    | name of the topic this message is sent to
   response              | name of the topic where response to this message is expected
+  forward               | name of the topic for a message forwarding
 meta                    | section for message meta information
   ttl                   | time to live of a message. If ttl is exceeded an incoming message is ignored
   createdAt             | timezone-aware date/time when message was created
diff --git a/release-notes.html b/release-notes.html
index 0be3eeed..4841456c 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -6,7 +6,7 @@
 
 

Welcome to MSB-Java version 1.4.3

-

February 08, 2016

+

February 09, 2016

MSB-Java version 1.4.3 contains minor improvements.
 
@@ -18,7 +18,7 @@ 

February 08, 2016

part before delimiter is a key and part after delimiter is a value. Delimiter is configurable. MDC is cleared after message handler invocation return; - it is possible to define a messages forwarding namespace for requests; - - Requester api now allows to publish message with multiple tags. + - Requester API now allows to publish message with multiple tags. Features of MSB-Java version 1.4.2: - the library uses only slf4j API for logging From 4c20b9b4aa72f8eb1ec3155c622df19fc09c7adf Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 9 Feb 2016 09:57:29 +0200 Subject: [PATCH 074/226] [maven-release-plugin] prepare release msb-java-1.4.3 --- acceptance/pom.xml | 4 ++-- amqp/pom.xml | 4 ++-- cli/pom.xml | 4 ++-- core/pom.xml | 4 ++-- examples/pom.xml | 4 ++-- pom.xml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 5efbeaab..ad2882a4 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.3 tcdl diff --git a/amqp/pom.xml b/amqp/pom.xml index e5a9524a..6787775c 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.3 tcdl diff --git a/cli/pom.xml b/cli/pom.xml index cc1d2708..81201f3d 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.3 tcdl diff --git a/core/pom.xml b/core/pom.xml index 6a1b0119..282b8c3d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.3 tcdl diff --git a/examples/pom.xml b/examples/pom.xml index c25fe248..f86d1839 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.3 diff --git a/pom.xml b/pom.xml index ac5c45c2..3c0d1afb 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.3-SNAPSHOT + 1.4.3 msb java msb java pom @@ -11,7 +11,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.3 From 7b3a86b779f4ee60b3e61bde47a8fd2da5f46e8c Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 9 Feb 2016 09:57:33 +0200 Subject: [PATCH 075/226] [maven-release-plugin] prepare for next development iteration --- acceptance/pom.xml | 4 ++-- amqp/pom.xml | 4 ++-- cli/pom.xml | 4 ++-- core/pom.xml | 4 ++-- examples/pom.xml | 4 ++-- pom.xml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index ad2882a4..fcadcf2b 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3 + 1.4.4-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.3 + HEAD tcdl diff --git a/amqp/pom.xml b/amqp/pom.xml index 6787775c..3ed6a755 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3 + 1.4.4-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.3 + HEAD tcdl diff --git a/cli/pom.xml b/cli/pom.xml index 81201f3d..1d4be600 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3 + 1.4.4-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.3 + HEAD tcdl diff --git a/core/pom.xml b/core/pom.xml index 282b8c3d..05661e5d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3 + 1.4.4-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.3 + HEAD tcdl diff --git a/examples/pom.xml b/examples/pom.xml index f86d1839..5bbef087 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.3 + 1.4.4-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.3 + HEAD diff --git a/pom.xml b/pom.xml index 3c0d1afb..5fe26ef6 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.3 + 1.4.4-SNAPSHOT msb java msb java pom @@ -11,7 +11,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.3 + HEAD From cbb3ed678408a87f768af7ed60a393e38bb663f4 Mon Sep 17 00:00:00 2001 From: anha1 Date: Thu, 11 Feb 2016 15:36:43 +0200 Subject: [PATCH 076/226] WEB-16957 msb-java mocking --- .../mock/MockAcknowledgementHandler.java | 37 ----- .../tcdl/msb/adapters/mock/MockAdapter.java | 152 ------------------ .../msb/adapters/mock/MockAdapterFactory.java | 51 ------ .../github/tcdl/msb/ChannelManagerTest.java | 3 +- .../adapters/AdapterFactoryLoaderTest.java | 6 +- .../adapters/mock/MockAdapterFactoryTest.java | 44 ----- .../msb/adapters/mock/MockAdapterTest.java | 79 --------- .../github/tcdl/msb/api/ChannelMonitorIT.java | 15 +- .../io/github/tcdl/msb/api/RequesterIT.java | 15 +- .../tcdl/msb/api/RequesterResponderIT.java | 48 +++--- .../io/github/tcdl/msb/api/ResponderIT.java | 13 +- .../adapterfactory/TestMsbAdapterFactory.java | 53 ++++++ .../TestMsbConsumerAdapter.java | 37 +++++ .../TestMsbProducerAdapter.java | 21 +++ .../TestMsbStorageForAdapterFactory.java | 116 +++++++++++++ .../mock/objectfactory/AbstractCapture.java | 34 ++++ .../mock/objectfactory/RequesterCapture.java | 73 +++++++++ .../mock/objectfactory/ResponderCapture.java | 37 +++++ .../objectfactory/TestMsbObjectFactory.java | 81 ++++++++++ .../TestMsbStorageForObjectFactory.java | 55 +++++++ core/src/test/resources/reference.conf | 2 +- doc/MSB-TEST-MOCKING.md | 116 +++++++++++++ doc/MSB.md | 5 + 23 files changed, 690 insertions(+), 403 deletions(-) delete mode 100644 core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAcknowledgementHandler.java delete mode 100644 core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java delete mode 100644 core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java delete mode 100644 core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactoryTest.java delete mode 100644 core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterTest.java create mode 100644 core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java create mode 100644 core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java create mode 100644 core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbProducerAdapter.java create mode 100644 core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java create mode 100644 core/src/test/java/io/github/tcdl/msb/mock/objectfactory/AbstractCapture.java create mode 100644 core/src/test/java/io/github/tcdl/msb/mock/objectfactory/RequesterCapture.java create mode 100644 core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java create mode 100644 core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java create mode 100644 core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java create mode 100644 doc/MSB-TEST-MOCKING.md diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAcknowledgementHandler.java b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAcknowledgementHandler.java deleted file mode 100644 index eea8e57f..00000000 --- a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAcknowledgementHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.tcdl.msb.adapters.mock; - -import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; - -public class MockAcknowledgementHandler implements AcknowledgementHandlerInternal { - @Override public void autoConfirm() { - - } - - @Override public void autoReject() { - - } - - @Override public void autoRetry() { - - } - - @Override public void setAutoAcknowledgement(boolean autoAcknowledgement) { - - } - - @Override public boolean isAutoAcknowledgement() { - return true; - } - - @Override public void confirmMessage() { - - } - - @Override public void retryMessage() { - - } - - @Override public void rejectMessage() { - - } -} diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java deleted file mode 100644 index 8c79ef33..00000000 --- a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapter.java +++ /dev/null @@ -1,152 +0,0 @@ -package io.github.tcdl.msb.adapters.mock; - -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.adapters.ProducerAdapter; -import io.github.tcdl.msb.api.exception.JsonConversionException; -import io.github.tcdl.msb.api.exception.JsonSchemaValidationException; -import io.github.tcdl.msb.support.JsonValidator; - -import java.io.IOException; -import java.util.Map; -import java.util.Queue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; - -/** - * MockAdapter class represents implementation of {@link ProducerAdapter} and {@link ConsumerAdapter} - * for test purposes. - */ -public class MockAdapter implements ProducerAdapter, ConsumerAdapter { - - private static final Logger LOG = LoggerFactory.getLogger(MockAdapter.class); - private static final int CONSUMING_INTERVAL = 20; - - static Map> messageMap = new ConcurrentHashMap<>(); - - private String topic; - private ExecutorService executorService; - private Queue activeConsumerExecutors; - private JsonValidator.JsonReader jsonReader = new JsonValidator.JsonReader(); - - public MockAdapter(String topic) { - LOG.debug("Created Mock Adapter for publishing to topic: " + topic); - this.topic = topic; - } - - public MockAdapter(String topic, Queue activeConsumerExecutors) { - LOG.debug("Created Mock Adapter for consuming form topic: " + topic); - this.topic = topic; - this.activeConsumerExecutors = activeConsumerExecutors; - this.executorService = activateConsumerThreadPool(topic); - } - - @Override - /** - * @throws ChannelException if an error is encountered during publishing to broker - */ - public void publish(String jsonMessage) { - LOG.debug("Received request {}", jsonMessage); - try { - JsonNode messageAsNode = jsonReader.read(jsonMessage); - if (!messageAsNode.has("topics")) { - throw new JsonSchemaValidationException(String.format("missing topics in message %s", jsonMessage)); - } - - JsonNode topicsAsNode = messageAsNode.get("topics"); - if (!topicsAsNode.has("to")) { - throw new JsonSchemaValidationException(String.format("missing topics.to in message %s", jsonMessage)); - } - - String topicsTo = topicsAsNode.get("to").asText(); - pushRequestMessage(topicsTo, jsonMessage); - } catch (IOException e) { - LOG.error("Received message can not be parsed"); - } - } - - @Override - public void subscribe(RawMessageHandler messageHandler) { - if (executorService == null) { - LOG.warn("Mock Adapter not initialized for consuming"); - } else { - executorService.execute(() -> { - { - String jsonMessage = null; - while (!executorService.isShutdown()) { - jsonMessage = pollJsonMessageForTopic(topic); - - if (messageHandler != null && jsonMessage != null) { - LOG.debug("Process message for topic {} [{}]", topic, jsonMessage); - messageHandler.onMessage(jsonMessage, new MockAcknowledgementHandler()); - } else { - try { - Thread.sleep(CONSUMING_INTERVAL); - } catch (Exception e) { - LOG.debug("Finish listen for subscribed topic"); - } - } - } - } - - }); - } - } - - @Override - public void unsubscribe() { - LOG.debug("Unsubscribe"); - if (executorService == null) { - LOG.warn("Mock Adapter not initialized for consuming"); - } - activeConsumerExecutors.remove(executorService); - executorService.shutdown(); - } - - public static String pollJsonMessageForTopic(String topic) { - String jsonMessage = null; - if (messageMap.get(topic) != null) { - jsonMessage = messageMap.get(topic).poll(); - } - - if (jsonMessage == null) { - LOG.debug("No message found for topic {}", topic); - } - return jsonMessage; - } - - public static void pushRequestMessage(String topicTo, String jsonMessage) { - Queue messagesQueue = messageMap.get(topicTo); - if (messagesQueue == null) { - messagesQueue = new ConcurrentLinkedQueue<>(); - Queue curQ = messageMap.putIfAbsent(topicTo, messagesQueue); - if (curQ != null) { - messagesQueue = curQ; - } - } - try { - messagesQueue.add(jsonMessage); - LOG.debug("Message for topic {} published: [{}]", topicTo, jsonMessage); - } catch (JsonConversionException e) { - LOG.error("Pushed message can not be parsed"); - } - } - - private ExecutorService activateConsumerThreadPool(String topic) { - BasicThreadFactory threadFactory = new BasicThreadFactory.Builder() - .namingPattern("mock-consumer-thread-%d") - .build(); - - ExecutorService executorService = Executors.newFixedThreadPool(1, threadFactory); - activeConsumerExecutors.add(executorService); - return executorService; - } - -} diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java deleted file mode 100644 index 78f0662f..00000000 --- a/core/src/main/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.tcdl.msb.adapters.mock; - -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutorService; -import io.github.tcdl.msb.impl.SimpleMessageHandlerInvokeStrategyImpl; -import io.github.tcdl.msb.adapters.AdapterFactory; -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy; -import io.github.tcdl.msb.adapters.ProducerAdapter; -import io.github.tcdl.msb.config.MsbConfig; -import io.github.tcdl.msb.support.Utils; - -/** - * MockAdapterFactory is an implementation of {@link AdapterFactory} - * for {@link MockAdapter} - */ -public class MockAdapterFactory implements AdapterFactory { - - Queue consumerExecutors = new ConcurrentLinkedQueue<>(); - - @Override - public void init(MsbConfig msbConfig) { - // No-op - } - - @Override - public ProducerAdapter createProducerAdapter(String topic) { - return new MockAdapter(topic); - } - - @Override - public ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) { - return new MockAdapter(topic, consumerExecutors); - } - - @Override - public MessageHandlerInvokeStrategy createMessageHandlerInvokeStrategy(String topic) { - return new SimpleMessageHandlerInvokeStrategyImpl(); - } - - @Override - public void shutdown() { - for (ExecutorService executorService : consumerExecutors) { - Utils.gracefulShutdown(executorService, "consumer"); - } - - consumerExecutors.clear(); - } - -} diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java index 147a42ea..4f6922aa 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java @@ -86,12 +86,13 @@ public void testReceiveMessageInvokesAgentAndEmitsEvent() throws InterruptedExce final Holder messageEvent = new Holder<>(); Message message = TestUtils.createSimpleRequestMessage(topic); - channelManager.findOrCreateProducer(topic).publish(message); + channelManager.subscribe(topic, (msg, acknowledgeHandler) -> { messageEvent.value = msg; awaitReceiveEvents.countDown(); }); + channelManager.findOrCreateProducer(topic).publish(message); assertTrue(awaitReceiveEvents.await(4000, TimeUnit.MILLISECONDS)); verify(mockChannelMonitorAgent).consumerMessageReceived(topic); diff --git a/core/src/test/java/io/github/tcdl/msb/adapters/AdapterFactoryLoaderTest.java b/core/src/test/java/io/github/tcdl/msb/adapters/AdapterFactoryLoaderTest.java index 761dfe9a..f6a315fa 100644 --- a/core/src/test/java/io/github/tcdl/msb/adapters/AdapterFactoryLoaderTest.java +++ b/core/src/test/java/io/github/tcdl/msb/adapters/AdapterFactoryLoaderTest.java @@ -7,9 +7,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import io.github.tcdl.msb.adapters.mock.MockAdapterFactory; import io.github.tcdl.msb.config.MsbConfig; +import io.github.tcdl.msb.mock.adapterfactory.TestMsbAdapterFactory; import org.junit.Before; import org.junit.Test; @@ -32,10 +32,10 @@ public void setUp() { @Test public void testCreatedMockAdapterByFactoryClassName(){ - when(msbConfigSpy.getBrokerAdapterFactory()).thenReturn("io.github.tcdl.msb.adapters.mock.MockAdapterFactory"); + when(msbConfigSpy.getBrokerAdapterFactory()).thenReturn("io.github.tcdl.msb.mock.adapterfactory.TestMsbAdapterFactory"); AdapterFactoryLoader loader = new AdapterFactoryLoader(msbConfigSpy); AdapterFactory adapterFactory = loader.getAdapterFactory(); - assertThat(adapterFactory, instanceOf(MockAdapterFactory.class)); + assertThat(adapterFactory, instanceOf(TestMsbAdapterFactory.class)); } @Test diff --git a/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactoryTest.java b/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactoryTest.java deleted file mode 100644 index 6f240453..00000000 --- a/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterFactoryTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.tcdl.msb.adapters.mock; - -import static org.hamcrest.core.IsInstanceOf.instanceOf; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -import io.github.tcdl.msb.adapters.AdapterFactory; -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.adapters.ProducerAdapter; -import org.junit.Test; - -/** - * MockAdapterFactory is an implementation of {@link AdapterFactory} - * for {@link MockAdapterTest} - */ -public class MockAdapterFactoryTest { - - @Test - public void testCreateConsumerAdapter() { - MockAdapterFactory mockAdapterFactory = new MockAdapterFactory(); - ConsumerAdapter consumer = mockAdapterFactory.createConsumerAdapter("", true); - assertThat(consumer, instanceOf(MockAdapter.class)); - assertTrue(mockAdapterFactory.consumerExecutors.size() == 1); - } - - @Test - public void testCreateProducerAdapter() { - MockAdapterFactory mockAdapterFactory = new MockAdapterFactory(); - ProducerAdapter producer = mockAdapterFactory.createProducerAdapter(""); - assertThat(producer, instanceOf(MockAdapter.class)); - assertTrue(mockAdapterFactory.consumerExecutors.size() == 0); - } - - @Test - public void testShutdown() { - MockAdapterFactory mockAdapterFactory = new MockAdapterFactory(); - mockAdapterFactory.createConsumerAdapter("", true); - assertTrue(mockAdapterFactory.consumerExecutors.size() == 1); - mockAdapterFactory.shutdown(); - assertTrue(mockAdapterFactory.consumerExecutors.size() == 0); - - } - -} diff --git a/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterTest.java b/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterTest.java deleted file mode 100644 index 5d37e038..00000000 --- a/core/src/test/java/io/github/tcdl/msb/adapters/mock/MockAdapterTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.github.tcdl.msb.adapters.mock; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.adapters.ProducerAdapter; -import io.github.tcdl.msb.api.exception.ChannelException; -import io.github.tcdl.msb.api.exception.JsonConversionException; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.support.TestUtils; -import io.github.tcdl.msb.support.Utils; - -import java.util.LinkedList; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutorService; - -import org.junit.Test; - -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * MockAdapter class represents implementation of {@link ProducerAdapter} and {@link ConsumerAdapter} - * for test purposes. - * - */ -public class MockAdapterTest { - - private ObjectMapper messageMapper = TestUtils.createMessageMapper(); - - @Test - public void testPublishAddMessageToMap() throws ChannelException, JsonConversionException { - String topic = "test:mock-adapter-publish"; - Message message = TestUtils.createSimpleRequestMessage(topic); - MockAdapter mockAdapter = new MockAdapter(topic); - - mockAdapter.publish(Utils.toJson(message, messageMapper)); - - assertNotNull(mockAdapter.messageMap.get(topic).poll()); - } - - @Test - public void testSubscribeCallMessageHandler() throws ChannelException, JsonConversionException { - String topic = "test:mock-adapter-subscribe"; - String message = Utils.toJson(TestUtils.createSimpleRequestMessage(topic), messageMapper); - Queue activeConsumerExecutors = new LinkedList<>(); - MockAdapter mockAdapter = new MockAdapter(topic, activeConsumerExecutors); - Queue messages = new ConcurrentLinkedQueue<>(); - messages.add(message); - mockAdapter.messageMap.put(topic, messages); - ConsumerAdapter.RawMessageHandler mockHandler = mock(ConsumerAdapter.RawMessageHandler.class); - - mockAdapter.subscribe(mockHandler); - - assertTrue(activeConsumerExecutors.size() == 1); - verify(mockHandler, timeout(500)).onMessage(eq(message), any()); - } - - @Test - public void testUnsubscribe() throws ChannelException, JsonConversionException { - String topic = "test:mock-adapter-unsubscribe"; - Queue activeConsumerExecutors = new LinkedList<>(); - MockAdapter mockAdapter = new MockAdapter(topic, activeConsumerExecutors); - - ConsumerAdapter.RawMessageHandler mockHandler = mock(ConsumerAdapter.RawMessageHandler.class); - mockAdapter.subscribe(mockHandler); - - assertTrue(activeConsumerExecutors.size() == 1); - mockAdapter.unsubscribe(); - assertTrue(activeConsumerExecutors.size() == 0); - - } - -} diff --git a/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java b/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java index 28f443ff..5ae8bae4 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java @@ -3,7 +3,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import io.github.tcdl.msb.adapters.mock.MockAdapter; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.api.monitor.AggregatorStats; @@ -23,6 +22,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import io.github.tcdl.msb.mock.adapterfactory.TestMsbStorageForAdapterFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -37,9 +37,12 @@ public class ChannelMonitorIT { MsbContextImpl msbContext; ChannelMonitorAggregator channelMonitorAggregator; + private TestMsbStorageForAdapterFactory storage; + @Before public void setUp() { msbContext = TestUtils.createSimpleMsbContext(); + storage = TestMsbStorageForAdapterFactory.extract(msbContext); } @After @@ -64,7 +67,7 @@ public void testAnnouncementUnexpectedMessage() throws InterruptedException { CountDownLatch announcementReceived = monitorPrepareAwaitOnAnnouncement(TOPIC_NAME); //simulate broken announcement in broker - MockAdapter.pushRequestMessage(Utils.TOPIC_ANNOUNCE, + storage.publishIncomingMessage(Utils.TOPIC_ANNOUNCE, Utils.toJson(TestUtils.createSimpleRequestMessage(Utils.TOPIC_ANNOUNCE), msbContext.getPayloadMapper())); assertFalse("Broken announcement was handled", announcementReceived.await(RequesterResponderIT.MESSAGE_TRANSMISSION_TIME / 2, TimeUnit.MILLISECONDS)); @@ -117,7 +120,7 @@ public void testHeartbeatMessage() throws InterruptedException { Message responseMessage = TestUtils.createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(), requestMessage.getCorrelationId(), payload); - MockAdapter.pushRequestMessage(requestMessage.getTopics().getResponse(), Utils.toJson(responseMessage, msbContext.getPayloadMapper())); + storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), Utils.toJson(responseMessage, msbContext.getPayloadMapper())); assertTrue("Heartbeat response was not received", heartBeatResponseReceived.await(HEARTBEAT_TIMEOUT_MS * 2, TimeUnit.MILLISECONDS)); @@ -156,9 +159,9 @@ public void testHeartbeatUnexpectedMessage() throws InterruptedException { .createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(), requestMessage.getCorrelationId(), payload); //simulate 3 heartbeatResponses: 1 valid and 2 broken - MockAdapter.pushRequestMessage(requestMessage.getTopics().getResponse(), Utils.toJson(brokenResponseMessage1, msbContext.getPayloadMapper())); - MockAdapter.pushRequestMessage(requestMessage.getTopics().getResponse(), Utils.toJson(responseMessage, msbContext.getPayloadMapper())); - MockAdapter.pushRequestMessage(requestMessage.getTopics().getResponse(), Utils.toJson(brokenResponseMessage2, msbContext.getPayloadMapper())); + storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), Utils.toJson(brokenResponseMessage1, msbContext.getPayloadMapper())); + storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), Utils.toJson(responseMessage, msbContext.getPayloadMapper())); + storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), Utils.toJson(brokenResponseMessage2, msbContext.getPayloadMapper())); assertTrue("Heartbeat response was not received", heartBeatResponseReceived.await(HEARTBEAT_TIMEOUT_MS * 2, TimeUnit.MILLISECONDS)); diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequesterIT.java b/core/src/test/java/io/github/tcdl/msb/api/RequesterIT.java index 2587bf2a..61692111 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/RequesterIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/RequesterIT.java @@ -3,11 +3,12 @@ import com.fasterxml.jackson.databind.JsonNode; import java.util.Base64; -import io.github.tcdl.msb.adapters.mock.MockAdapter; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.impl.MsbContextImpl; import io.github.tcdl.msb.support.TestUtils; +import io.github.tcdl.msb.mock.adapterfactory.TestMsbStorageForAdapterFactory; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -21,11 +22,13 @@ public class RequesterIT { private RequestOptions requestOptions; private MsbContextImpl msbContext; + private TestMsbStorageForAdapterFactory storage; @Before public void setUp() throws Exception { this.requestOptions = TestUtils.createSimpleRequestOptionsWithTags(STATIC_TAG); this.msbContext = TestUtils.createSimpleMsbContext(); + storage = TestMsbStorageForAdapterFactory.extract(msbContext); } @Test @@ -34,7 +37,7 @@ public void testRequestMessage() throws Exception { Requester requester = msbContext.getObjectFactory().createRequester(NAMESPACE, requestOptions); requester.publish(requestPayload); - String adapterJsonMessage = MockAdapter.pollJsonMessageForTopic(NAMESPACE); + String adapterJsonMessage = storage.getOutgoingMessage(NAMESPACE); TestUtils.assertRequestMessagePayload(adapterJsonMessage, requestPayload, NAMESPACE); } @@ -45,7 +48,7 @@ public void testRequestMessageWithBodyBufferBase64Encoded() throws Exception { Requester requester = msbContext.getObjectFactory().createRequester(NAMESPACE, requestOptions); requester.publish(requestPayload); - String adapterJsonMessage = MockAdapter.pollJsonMessageForTopic(NAMESPACE); + String adapterJsonMessage = storage.getOutgoingMessage(NAMESPACE); JsonNode jsonObject = msbContext.getPayloadMapper().readTree(adapterJsonMessage); String base64Encoded = Base64.getEncoder().encodeToString(bytesToSend); @@ -59,7 +62,7 @@ public void testRequestMessageWithDynamicTag() throws Exception { Requester requester = msbContext.getObjectFactory().createRequester(NAMESPACE, requestOptions); requester.publish(requestPayload, dynamicTag); - String adapterJsonMessage = MockAdapter.pollJsonMessageForTopic(NAMESPACE); + String adapterJsonMessage = storage.getOutgoingMessage(NAMESPACE); TestUtils.assertRequestMessagePayload(adapterJsonMessage, requestPayload, NAMESPACE); TestUtils.assertMessageTags(adapterJsonMessage, STATIC_TAG, dynamicTag); } @@ -73,7 +76,7 @@ public void testRequestMessageWithDynamicTagAndOriginalMessage() throws Exceptio Requester requester = msbContext.getObjectFactory().createRequester(NAMESPACE, requestOptions); requester.publish(requestPayload, originalMessage, dynamicTag); - String adapterJsonMessage = MockAdapter.pollJsonMessageForTopic(NAMESPACE); + String adapterJsonMessage = storage.getOutgoingMessage(NAMESPACE); TestUtils.assertRequestMessagePayload(adapterJsonMessage, requestPayload, NAMESPACE); TestUtils.assertMessageTags(adapterJsonMessage, dynamicTagOriginal, STATIC_TAG, dynamicTag); } @@ -87,7 +90,7 @@ public void testRequestMessageWithDuplicateTagInOriginalMessage() throws Excepti Requester requester = msbContext.getObjectFactory().createRequester(NAMESPACE, requestOptions); requester.publish(requestPayload, originalMessage, dynamicTag); - String adapterJsonMessage = MockAdapter.pollJsonMessageForTopic(NAMESPACE); + String adapterJsonMessage = storage.getOutgoingMessage(NAMESPACE); TestUtils.assertRequestMessagePayload(adapterJsonMessage, requestPayload, NAMESPACE); TestUtils.assertMessageTags(adapterJsonMessage, dynamicTagOriginal, STATIC_TAG, dynamicTag); } diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java index e26384d2..d003d94f 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java @@ -3,7 +3,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import io.github.tcdl.msb.adapters.mock.MockAdapter; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.impl.MsbContextImpl; @@ -20,6 +19,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import io.github.tcdl.msb.mock.adapterfactory.TestMsbStorageForAdapterFactory; import org.junit.Before; import org.junit.Test; @@ -36,10 +36,12 @@ public class RequesterResponderIT { public static final int MESSAGE_ROUNDTRIP_TRANSMISSION_TIME = MESSAGE_TRANSMISSION_TIME * 2; private MsbContextImpl msbContext; + private TestMsbStorageForAdapterFactory storage; @Before public void setUp() throws Exception { - this.msbContext = TestUtils.createSimpleMsbContext(); + msbContext = TestUtils.createSimpleMsbContext(); + storage = TestMsbStorageForAdapterFactory.extract(msbContext); } @Test @@ -96,6 +98,16 @@ public void testResponderAnswerWithAckRequesterReceiveAck() throws Exception { List receivedResponseAcks = new LinkedList<>(); + //listen for message and send ack + MsbContextImpl serverMsbContext = TestUtils.createSimpleMsbContext(); + storage.connect(serverMsbContext); + + serverMsbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, responderContext) -> { + responderContext.getResponder().sendAck(100, 2); + ackSend.countDown(); + }) + .listen(); + //Create and send request message directly to broker, wait for ack RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); msbContext.getObjectFactory().createRequester(namespace, requestOptions). @@ -105,14 +117,6 @@ public void testResponderAnswerWithAckRequesterReceiveAck() throws Exception { }) .publish(requestPayload); - //listen for message and send ack - MsbContextImpl serverMsbContext = TestUtils.createSimpleMsbContext(); - serverMsbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, responderContext) -> { - responderContext.getResponder().sendAck(100, 2); - ackSend.countDown(); - }) - .listen(); - assertTrue("Message ack was not send", ackSend.await(MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); assertTrue("Message ack response not received", ackResponseReceived.await(MESSAGE_ROUNDTRIP_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); assertTrue("Expected one ack", receivedResponseAcks.size() == 1); @@ -134,23 +138,25 @@ public void testResponderAnswerWithResponseRequesterReceiveCustomPayloadResponse String requestPayload = "request payload"; String responsePayload = "response payload"; - //Create and send request message directly to broker, wait for response - msbContext.getObjectFactory().createRequester(namespace, requestOptions, String.class) - .onResponse((payload, ackHandler) -> { - receivedResponses.add(payload); - respReceived.countDown(); - assertEquals(responsePayload, payload); - }) - .publish(requestPayload); //listen for message and send response MsbContextImpl serverMsbContext = TestUtils.createSimpleMsbContext(); + storage.connect(serverMsbContext); serverMsbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, responderContext) -> { responderContext.getResponder().send(responsePayload); respSent.countDown(); }, String.class).listen(); + //Create and send request message directly to broker, wait for response + msbContext.getObjectFactory().createRequester(namespace, requestOptions, String.class) + .onResponse((payload, ackHandler) -> { + receivedResponses.add(payload); + respReceived.countDown(); + assertEquals(responsePayload, payload); + }) + .publish(requestPayload); + assertTrue("Message response was not send", respSent.await(MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); assertTrue("Message response not received", respReceived.await(MESSAGE_ROUNDTRIP_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); assertTrue("Expected one response", receivedResponses.size() == 1); @@ -172,6 +178,7 @@ public void testResponderCommunicationWithAck() throws Exception { CountDownLatch ackReceived = new CountDownLatch(1); MsbContextImpl serverOneMsbContext = TestUtils.createSimpleMsbContext(); + storage.connect(serverOneMsbContext); serverOneMsbContext.getObjectFactory().createResponderServer(namespace1, responderServerOneMessageOptions, (request, response) -> { //Create and send request message, wait for ack @@ -183,6 +190,7 @@ public void testResponderCommunicationWithAck() throws Exception { .listen(); MsbContextImpl serverTwoMsbContext = TestUtils.createSimpleMsbContext(); + storage.connect(serverTwoMsbContext); serverTwoMsbContext.getObjectFactory().createResponderServer(namespace2, responderServerTwoMessageOptions, (request, responderContext) -> { responderContext.getResponder().sendAck(100, 2); @@ -190,7 +198,7 @@ public void testResponderCommunicationWithAck() throws Exception { }) .listen(); - MockAdapter.pushRequestMessage(namespace1, + storage.publishIncomingMessage(namespace1, Utils.toJson(TestUtils.createSimpleRequestMessage(namespace1), msbContext.getPayloadMapper())); assertTrue("Message ack was not send", ackSent.await(MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); @@ -235,6 +243,8 @@ public void testMultipleRequesterListenForAcks() throws Exception { //listen for message and send ack MsbContextImpl serverMsbContext = TestUtils.createSimpleMsbContext(); + storage.connect(serverMsbContext); + Random randomAckValue = new Random(); randomAckValue.ints(); serverMsbContext.getObjectFactory().createResponderServer(namespace, requestOptions.getMessageTemplate(), diff --git a/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java index a6601798..13214acd 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/ResponderIT.java @@ -5,7 +5,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import io.github.tcdl.msb.adapters.mock.MockAdapter; import io.github.tcdl.msb.api.exception.JsonSchemaValidationException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; @@ -16,6 +15,8 @@ import java.io.IOException; +import io.github.tcdl.msb.mock.adapterfactory.TestMsbStorageForAdapterFactory; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; @@ -33,14 +34,18 @@ public class ResponderIT { private MsbContextImpl msbContext; private JsonValidator validator; private ObjectMapper payloadMapper; + private TestMsbStorageForAdapterFactory storage; @Before public void setUp() throws Exception { msbContext = (MsbContextImpl) new MsbContextBuilder().build(); + storage = TestMsbStorageForAdapterFactory.extract(msbContext); validator = new JsonValidator(); payloadMapper = msbContext.getPayloadMapper(); } + + @Test public void testCreateAckMessage() throws Exception { String namespace = "test:responder-ack"; @@ -53,7 +58,7 @@ public void testCreateAckMessage() throws Exception { responder.sendAck(ackTimeout, responsesRemaining); - String adapterJsonMessage = MockAdapter.pollJsonMessageForTopic(originalMessage.getTopics().getResponse()); + String adapterJsonMessage = storage.getOutgoingMessage(originalMessage.getTopics().getResponse()); assertNotNull("Ack message shouldn't be null", adapterJsonMessage); assertAckMessage(adapterJsonMessage, ackTimeout, responsesRemaining, originalMessage.getTopics().getResponse()); @@ -94,7 +99,7 @@ public void testCreateResponseMessage() throws Exception { RestPayload responsePayload = TestUtils.createSimpleResponsePayload(); responder.send(responsePayload); - String adapterJsonMessage = MockAdapter.pollJsonMessageForTopic(originalMessage.getTopics().getResponse()); + String adapterJsonMessage = storage.getOutgoingMessage(originalMessage.getTopics().getResponse()); assertNotNull("Response message shouldn't be null", adapterJsonMessage); TestUtils.assertResponseMessagePayload(adapterJsonMessage, responsePayload, originalMessage.getTopics().getResponse()); @@ -111,7 +116,7 @@ public void testCreateResponseMessageWithTags() throws Exception { RestPayload responsePayload = TestUtils.createSimpleResponsePayload(); responder.send(responsePayload); - String adapterJsonMessage = MockAdapter.pollJsonMessageForTopic(originalMessage.getTopics().getResponse()); + String adapterJsonMessage = storage.getOutgoingMessage(originalMessage.getTopics().getResponse()); assertNotNull("Response message shouldn't be null", adapterJsonMessage); TestUtils.assertResponseMessagePayload(adapterJsonMessage, responsePayload, originalMessage.getTopics().getResponse()); diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java new file mode 100644 index 00000000..98bfff13 --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java @@ -0,0 +1,53 @@ +package io.github.tcdl.msb.mock.adapterfactory; + +import io.github.tcdl.msb.adapters.AdapterFactory; +import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy; +import io.github.tcdl.msb.adapters.ProducerAdapter; +import io.github.tcdl.msb.config.MsbConfig; +import io.github.tcdl.msb.impl.SimpleMessageHandlerInvokeStrategyImpl; + +/** + * This AdapterFactory implementation is used to capture/submit raw messages as JSON and could be used during testing. + */ +public class TestMsbAdapterFactory implements AdapterFactory { + + private TestMsbStorageForAdapterFactory storage = new TestMsbStorageForAdapterFactory(); + + public TestMsbStorageForAdapterFactory getStorage() { + return storage; + } + + public void setStorage(TestMsbStorageForAdapterFactory storage) { + this.storage = storage; + } + + @Override + public void init(MsbConfig msbConfig) { + + } + + @Override + public ProducerAdapter createProducerAdapter(String namespace) { + TestMsbProducerAdapter producerAdapter = new TestMsbProducerAdapter(namespace, storage); + storage.addProducerAdapter(namespace, producerAdapter); + return producerAdapter; + } + + @Override + public ConsumerAdapter createConsumerAdapter(String namespace, boolean isResponseTopic) { + TestMsbConsumerAdapter consumerAdapter = new TestMsbConsumerAdapter(namespace, storage); + storage.addConsumerAdapter(namespace, consumerAdapter); + return consumerAdapter; + } + + @Override + public MessageHandlerInvokeStrategy createMessageHandlerInvokeStrategy(String topic) { + return new SimpleMessageHandlerInvokeStrategyImpl(); + } + + @Override + public void shutdown() { + + } +} diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java new file mode 100644 index 00000000..439deb28 --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java @@ -0,0 +1,37 @@ +package io.github.tcdl.msb.mock.adapterfactory; + +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; +import io.github.tcdl.msb.adapters.ConsumerAdapter; + +import java.util.HashSet; +import java.util.Set; + +import static org.mockito.Mockito.mock; + +public class TestMsbConsumerAdapter implements ConsumerAdapter { + + private final Set rawMessageHandlers = new HashSet<>(); + private final TestMsbStorageForAdapterFactory storage; + + private final String namespace; + + public TestMsbConsumerAdapter(String namespace, TestMsbStorageForAdapterFactory storage) { + this.namespace = namespace; + this.storage = storage; + } + + @Override + public void subscribe(RawMessageHandler onMessageHandler) { + rawMessageHandlers.add(onMessageHandler); + } + + @Override + public void unsubscribe() { + + } + + public void pushTestMessage(String jsonMessage) { + AcknowledgementHandlerInternal ackHandler = mock(AcknowledgementHandlerInternal.class); + rawMessageHandlers.forEach((handler)-> handler.onMessage(jsonMessage, ackHandler)); + } +} diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbProducerAdapter.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbProducerAdapter.java new file mode 100644 index 00000000..7ec8a0e9 --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbProducerAdapter.java @@ -0,0 +1,21 @@ +package io.github.tcdl.msb.mock.adapterfactory; + +import io.github.tcdl.msb.adapters.ProducerAdapter; + +public class TestMsbProducerAdapter implements ProducerAdapter { + + private final String namespace; + + private final TestMsbStorageForAdapterFactory storage; + + public TestMsbProducerAdapter(String namespace, TestMsbStorageForAdapterFactory storage) { + this.namespace = namespace; + this.storage = storage; + } + + @Override + public void publish(String jsonMessage) { + storage.addPublishedTestMessage(namespace, jsonMessage); + storage.publishIncomingMessage(namespace, jsonMessage); + } +} diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java new file mode 100644 index 00000000..fe17247d --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java @@ -0,0 +1,116 @@ +package io.github.tcdl.msb.mock.adapterfactory; + +import io.github.tcdl.msb.ChannelManager; +import io.github.tcdl.msb.api.MsbContext; +import org.apache.commons.lang3.reflect.FieldUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +/** + * This class provides statics-based storage and accessors for {@link TestMsbAdapterFactory}-based + * testing. + */ +public class TestMsbStorageForAdapterFactory { + private final HashMap consumers = new HashMap<>(); + private final HashMap producers = new HashMap<>(); + private final HashMap> publishedMessages = new HashMap<>(); + + /** + * Get TestMsbStorageForAdapterFactory instance used by a MsbContext. + * @param msbContext + * @return + */ + public static TestMsbStorageForAdapterFactory extract(MsbContext msbContext) { + try { + ChannelManager channelManager = (ChannelManager) FieldUtils.readField(msbContext, "channelManager", true); + TestMsbAdapterFactory adapterFactory = (TestMsbAdapterFactory) FieldUtils.readField(channelManager, "adapterFactory", true); + return adapterFactory.getStorage(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + /** + * Force other context to use this TestMsbStorageForAdapterFactory instance so messaging will be shared + * between different contexts. Without this action, an MsbContext handles its own messages only. + * @param otherContext + */ + public void connect(MsbContext otherContext) { + try { + ChannelManager channelManager = (ChannelManager) FieldUtils.readField(otherContext, "channelManager", true); + TestMsbAdapterFactory adapterFactory = (TestMsbAdapterFactory) FieldUtils.readField(channelManager, "adapterFactory", true); + adapterFactory.setStorage(this); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + void addProducerAdapter(String namespace, TestMsbProducerAdapter adapter) { + producers.put(namespace, adapter); + } + + void addConsumerAdapter(String namespace, TestMsbConsumerAdapter adapter) { + consumers.put(namespace, adapter); + } + + void addPublishedTestMessage(String namespace, String jsonMessage) { + List publishedMessages = this.publishedMessages.get(namespace); + if (publishedMessages == null) { + publishedMessages = new ArrayList<>(); + this.publishedMessages.put(namespace, publishedMessages); + publishedMessages.add(jsonMessage); + } + } + + + /** + * Reset the storage. + */ + public void cleanup() { + consumers.clear(); + producers.clear(); + publishedMessages.clear(); + } + + /** + * Publish a raw JSON message that should be handled as an incoming message. + * @param namespace + * @param jsonMessage + */ + public void publishIncomingMessage(String namespace, String jsonMessage) { + TestMsbConsumerAdapter consumerAdapter = consumers.get(namespace); + if(consumerAdapter != null) { + consumerAdapter.pushTestMessage(jsonMessage); + } + } + + /** + * Get a list of outgoing raw JSON messages. + * @param namespace + * @return + */ + public List getOutgoingMessages(String namespace) { + List publishedMessages = this.publishedMessages.get(namespace); + if (publishedMessages == null) { + publishedMessages = Collections.emptyList(); + } + return publishedMessages; + } + + /** + * Get a single outgoing raw JSON message. + * @param namespace + * @return + */ + public String getOutgoingMessage(String namespace) { + List publishedMessages = this.publishedMessages.get(namespace); + if (publishedMessages == null || publishedMessages.size() != 1) { + throw new RuntimeException("A single outgoing message is expected"); + } + return publishedMessages.get(0); + } + +} diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/AbstractCapture.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/AbstractCapture.java new file mode 100644 index 00000000..a099349d --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/AbstractCapture.java @@ -0,0 +1,34 @@ +package io.github.tcdl.msb.mock.objectfactory; + +import com.fasterxml.jackson.core.type.TypeReference; + +/** + * Abstract capture class for requesters and responders. + * @param + */ +public abstract class AbstractCapture { + private final String namespace; + private final TypeReference payloadTypeReference; + private final Class payloadClass; + + public AbstractCapture(String namespace, + TypeReference payloadTypeReference, Class payloadClass) { + this.namespace = namespace; + this.payloadTypeReference = payloadTypeReference; + this.payloadClass = payloadClass; + } + + public String getNamespace() { + return namespace; + } + + public TypeReference getPayloadTypeReference() { + return payloadTypeReference; + } + + public Class getPayloadClass() { + return payloadClass; + } + + +} \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/RequesterCapture.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/RequesterCapture.java new file mode 100644 index 00000000..3f5aa763 --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/RequesterCapture.java @@ -0,0 +1,73 @@ +package io.github.tcdl.msb.mock.objectfactory; + +import com.fasterxml.jackson.core.type.TypeReference; +import io.github.tcdl.msb.api.Callback; +import io.github.tcdl.msb.api.MessageContext; +import io.github.tcdl.msb.api.RequestOptions; +import io.github.tcdl.msb.api.Requester; +import io.github.tcdl.msb.api.message.Acknowledge; +import io.github.tcdl.msb.api.message.Message; +import org.mockito.ArgumentCaptor; + +import java.util.function.BiConsumer; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Captured params of a requester. Handlers are captured by {@link ArgumentCaptor}. + * @param + */ +public class RequesterCapture extends AbstractCapture { + private final RequestOptions requestOptions; + private final Requester requesterMock; + + private final ArgumentCaptor> onAcknowledgeCaptor + = ArgumentCaptor.forClass((Class>)(Class)BiConsumer.class); + + private final ArgumentCaptor> onResponseCaptor + = ArgumentCaptor.forClass((Class>)(Class)BiConsumer.class); + + private final ArgumentCaptor> onRawResponseCaptor + = ArgumentCaptor.forClass((Class>)(Class)BiConsumer.class); + + private final ArgumentCaptor> onEndCaptor + = ArgumentCaptor.forClass((Class>)(Class)Callback.class); + + public RequesterCapture(String namespace, RequestOptions requestOptions, + TypeReference payloadTypeReference, Class payloadClass) { + super(namespace, payloadTypeReference, payloadClass); + this.requestOptions = requestOptions; + this.requesterMock = mock(Requester.class); + + when(this.requesterMock.onAcknowledge(onAcknowledgeCaptor.capture())).thenReturn(this.requesterMock); + when(this.requesterMock.onResponse(onResponseCaptor.capture())).thenReturn(this.requesterMock); + when(this.requesterMock.onRawResponse(onRawResponseCaptor.capture())).thenReturn(this.requesterMock); + when(this.requesterMock.onEnd(onEndCaptor.capture())).thenReturn(this.requesterMock); + + } + + public RequestOptions getRequestOptions() { + return requestOptions; + } + + public Requester getRequesterMock() { + return requesterMock; + } + + public ArgumentCaptor> getOnAcknowledgeCaptor() { + return onAcknowledgeCaptor; + } + + public ArgumentCaptor> getOnResponseCaptor() { + return onResponseCaptor; + } + + public ArgumentCaptor> getOnRawResponseCaptor() { + return onRawResponseCaptor; + } + + public ArgumentCaptor> getOnEndCaptor() { + return onEndCaptor; + } +} \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java new file mode 100644 index 00000000..bb17e30d --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java @@ -0,0 +1,37 @@ +package io.github.tcdl.msb.mock.objectfactory; + +import com.fasterxml.jackson.core.type.TypeReference; +import io.github.tcdl.msb.api.MessageTemplate; +import io.github.tcdl.msb.api.ResponderServer; + +import static org.mockito.Mockito.mock; + +/** + * Captured params of a responder, including a requestHandler. + * @param + */ +public class ResponderCapture extends AbstractCapture { + private final MessageTemplate messageTemplate; + private final ResponderServer.RequestHandler requestHandler; + private final ResponderServer responderServerMock; + + public ResponderCapture(String namespace, MessageTemplate messageTemplate, ResponderServer.RequestHandler requestHandler, + TypeReference payloadTypeReference, Class payloadClass) { + super(namespace, payloadTypeReference, payloadClass); + this.messageTemplate = messageTemplate; + this.requestHandler = requestHandler; + this.responderServerMock = mock(ResponderServer.class); + } + + public MessageTemplate getMessageTemplate() { + return messageTemplate; + } + + public ResponderServer.RequestHandler getRequestHandler() { + return requestHandler; + } + + public ResponderServer getResponderServerMock() { + return responderServerMock; + } +} \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java new file mode 100644 index 00000000..2c6de266 --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java @@ -0,0 +1,81 @@ +package io.github.tcdl.msb.mock.objectfactory; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import io.github.tcdl.msb.api.*; +import io.github.tcdl.msb.api.monitor.AggregatorStats; +import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; + +/** + * {@link ObjectFactory} implementation that captures all requesters/responders params and callbacks to be + * used during testing. + */ +public class TestMsbObjectFactory implements ObjectFactory { + + private final TestMsbStorageForObjectFactory storage = new TestMsbStorageForObjectFactory(); + + public TestMsbStorageForObjectFactory getStorage() { + return storage; + } + + @Override + public Requester createRequester(String namespace, RequestOptions requestOptions, TypeReference payloadTypeReference) { + RequesterCapture capture = new RequesterCapture<>(namespace, requestOptions, payloadTypeReference, null); + storage.addCapture(capture); + return capture.getRequesterMock(); + } + + @Override + public Requester createRequester(String namespace, RequestOptions requestOptions) { + RequesterCapture capture = new RequesterCapture<>(namespace, requestOptions, null, null); + storage.addCapture(capture); + return capture.getRequesterMock(); + } + + @Override + public Requester createRequester(String namespace, RequestOptions requestOptions, Class payloadClass) { + RequesterCapture capture = new RequesterCapture<>(namespace, requestOptions, null, payloadClass); + storage.addCapture(capture); + return capture.getRequesterMock(); + } + + @Override + public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, + ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) { + ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, payloadTypeReference, null); + storage.addCapture(capture); + return capture.getResponderServerMock(); + } + + @Override + public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, + ResponderServer.RequestHandler requestHandler) { + ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, null); + storage.addCapture(capture); + return capture.getResponderServerMock(); + } + + @Override + public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, + ResponderServer.RequestHandler requestHandler, Class payloadClass) { + ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, payloadClass); + storage.addCapture(capture); + return capture.getResponderServerMock(); + } + + @Override + public PayloadConverter getPayloadConverter() { + return null; + } + + @Override + public ChannelMonitorAggregator createChannelMonitorAggregator(Callback aggregatorStatsHandler) { + return null; + } + + @Override + public void shutdown() { + + } + +} diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java new file mode 100644 index 00000000..07ff48ed --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java @@ -0,0 +1,55 @@ +package io.github.tcdl.msb.mock.objectfactory; +import io.github.tcdl.msb.api.MsbContext; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class provides statics-based storage and accessors for {@link TestMsbObjectFactory}-based + * testing. + */ +public class TestMsbStorageForObjectFactory { + private final Map requesters = new HashMap<>(); + private final Map responders = new HashMap<>(); + + public static TestMsbStorageForObjectFactory extract(MsbContext msbContext) { + return ((TestMsbObjectFactory)msbContext.getObjectFactory()).getStorage(); + } + + void addCapture(RequesterCapture requesterCapture) { + requesters.put(requesterCapture.getNamespace(), requesterCapture); + } + + void addCapture(ResponderCapture responderCapture) { + responders.put(responderCapture.getNamespace(), responderCapture); + } + + + /** + * Reset the storage. + */ + public void cleanup() { + requesters.clear(); + responders.clear(); + } + + /** + * Get captured requester params (including handlers). + * @param namespace + * @param + * @return + */ + public RequesterCapture getRequesterCapture(String namespace) { + return (RequesterCapture) requesters.get(namespace); + } + + /** + * Get captured responder params (including handlers). + * @param namespace + * @param + * @return + */ + public ResponderCapture getResponderCapture(String namespace) { + return (ResponderCapture) responders.get(namespace); + } +} diff --git a/core/src/test/resources/reference.conf b/core/src/test/resources/reference.conf index 30c018b4..483858a1 100644 --- a/core/src/test/resources/reference.conf +++ b/core/src/test/resources/reference.conf @@ -13,7 +13,7 @@ msbConfig { # Enable/disable message validation against json schema validateMessage = true - brokerAdapterFactory = "io.github.tcdl.msb.adapters.mock.MockAdapterFactory" # in memory broker + brokerAdapterFactory = "io.github.tcdl.msb.mock.adapterfactory.TestMsbAdapterFactory" # in memory broker # Broker Adapter Defaults brokerConfig = { diff --git a/doc/MSB-TEST-MOCKING.md b/doc/MSB-TEST-MOCKING.md new file mode 100644 index 00000000..28940ff0 --- /dev/null +++ b/doc/MSB-TEST-MOCKING.md @@ -0,0 +1,116 @@ +# MSB-Java mocking support +This documents describes experimental MSB-Java mocking approaches. +There are two different testing approaches available: AdapterFactory-based and ObjectFactory-based. +Both of them are Mockito-based. + +## Maven dependency +In order to use mocking utilities described, please use the following Maven dependency: +``` + + io.github.tcdl.msb + msb-java-core + test-jar + ${msb.version} + test + +``` + +## ObjectFactory-based approach +**Related package:** _io.github.tcdl.msb.mock.objectfactory_ + +This type of test support is used to test client's code. For example, it gives an ability +to invoke incoming handlers directly (with a custom test payload as an argument). +So MSB internal code is not involved in the testing. + +Typical usage: + - Inject mocked MsbContext configured to return TestMsbObjectFactory and extract a storage from this mock: + +``` java +@Mock +private MsbContext msbContext; +private TestMsbStorageForObjectFactory storage; +... +@Before +public void setUp() throws Exception { + when(msbContext.getObjectFactory()) + .thenReturn(new TestMsbObjectFactory()); + storage = TestMsbStorageForObjectFactory.extract(msbContext); + ... +} +``` + + - Perform captured requester testing (including direct handlers invocations): +``` java +RequesterCapture requesterCapture = + storage.getRequesterCapture("my:namespace:out"); +assertEquals(MyPalyload.class, requesterCapture.getPayloadClass()); + +//invoke onEnd handler +Callback onEndHandler = requesterCapture.getOnEndCaptor().getValue(); +onEndHandler.call(null); + +//invoke onResponse handler +BiConsumer onResponseHandler = requesterCapture.getOnResponseCaptor().getValue(); +onResponseHandler.accept(myPalyload, messageContextMock); +``` + + - Perform captured responder testing (including direct handlers invocations): +``` java +ResponderCapture responderCapture = storage.getResponderCapture("my:namespace:in"); +assertEquals(MyPalyload.class, responderCapture.getPayloadClass()); + +//invoke requestHandler +ResponderServer.RequestHandler requestHandler = responderCapture.getRequestHandler(); +requestHandler.process(myPalyload, responderContextMock); +``` + +## AdapterFactory-based approach +**Related package:** _io.github.tcdl.msb.mock.adapterfactory_ + +This type of test support is used to test the flow of an incoming message through all MSB layers. +Using this option makes it possible both submit a raw message JSON into a namespace, and capture outgoing messages raw JSON. + +Typical usage: + - Configure Msb to use the test AdapterFactory implementation: +``` +msbConfig { + brokerAdapterFactory = "io.github.tcdl.msb.mock.adapterfactory.TestMsbAdapterFactory" +} +``` + + - Inject non-mocked MsbContext that is using this configuration and extract a storage from this mock: +``` java +private MsbContext msbContext; +private TestMsbStorageForAdapterFactory storage; +... +@Before +public void setUp() throws Exception { + msbContext = TestUtils.createSimpleMsbContext(); + storage = TestMsbStorageForAdapterFactory.extract(msbContext); +} + +``` + + - Submit incoming messages JSON using: +``` java +storage.publishIncomingMessage("my:incoming:namespace", "{my: 'message_json'}"); +``` + + - Verify outgoing message JSON using: +``` java +List publishedTestMessages = storage.getOutgoingMessages("my:out:namespace"); +String publishedMessage = storage.getOutgoingMessage("my:out:namespace"); +``` + + - During the testing, perform JSON String - Message conversions using MSB utilities if required: +``` java +Utils.fromJson(json, Message.class, objectMapper); +Utils.toJson(message, objectMapper); +``` + + - If several MsbContext instances are involved into the testing, they will be able to handle only their own messages. + In order to use shared messaging, it is required to connect MsbContext instances to a single storage: +``` java +MsbContext otherContext = TestUtils.createSimpleMsbContext(); +storage.connect(otherContext); +``` diff --git a/doc/MSB.md b/doc/MSB.md index 79040c9c..a0dd3ebe 100644 --- a/doc/MSB.md +++ b/doc/MSB.md @@ -250,6 +250,11 @@ Another caveat is that the business logic of `PongService` (passed as lambda in See [Readme for CLI tool](/cli/README.md). + +## Test support - mocking approaches. + +See [MSB-Java mocking support](MSB-TEST-MOCKING.md) + # Appendix ## Configuration From 280d2f9908b58569f8a48e0243ae3eeb5deeef93 Mon Sep 17 00:00:00 2001 From: anha1 Date: Sun, 14 Feb 2016 20:03:33 +0200 Subject: [PATCH 077/226] WEB-16957 msb-java mocking --- .../java/io/github/tcdl/msb/api/RequesterResponderIT.java | 5 +++++ .../mock/adapterfactory/TestMsbStorageForAdapterFactory.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java index d003d94f..dcc18957 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java @@ -268,16 +268,21 @@ public void testRequestMessageCollectorUnsubscribeAfterResponsesAndSubscribeAgai .withWaitForResponses(1) .build(); + final CountDownLatch daemonListens = new CountDownLatch(1); + Thread serverListenThread = new Thread(() -> { msbContext.getObjectFactory().createResponderServer(namespace, requestOptionsWaitResponse.getMessageTemplate(), (request, responderContext) -> responderContext.getResponder().send("payload from test : testRequestMessageCollectorUnsubscribeAfterResponsesAndSubscribeAgain") ) .listen(); + daemonListens.countDown(); }); serverListenThread.setDaemon(true); serverListenThread.start(); + daemonListens.await(5000, TimeUnit.MILLISECONDS); + CountDownLatch endConversation1 = new CountDownLatch(1); CountDownLatch endConversation2 = new CountDownLatch(1); diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java index fe17247d..515f6d7a 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java @@ -61,8 +61,8 @@ void addPublishedTestMessage(String namespace, String jsonMessage) { if (publishedMessages == null) { publishedMessages = new ArrayList<>(); this.publishedMessages.put(namespace, publishedMessages); - publishedMessages.add(jsonMessage); } + publishedMessages.add(jsonMessage); } From e1f6d8a18e51f5d57da155a760a0bd0cadd284fa Mon Sep 17 00:00:00 2001 From: anha1 Date: Sun, 14 Feb 2016 21:12:51 +0200 Subject: [PATCH 078/226] WEB-16957 msb-java mocking - synchronized methods --- .../TestMsbStorageForAdapterFactory.java | 14 +++++++------- .../TestMsbStorageForObjectFactory.java | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java index 515f6d7a..7b50a389 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java @@ -48,15 +48,15 @@ public void connect(MsbContext otherContext) { } } - void addProducerAdapter(String namespace, TestMsbProducerAdapter adapter) { + synchronized void addProducerAdapter(String namespace, TestMsbProducerAdapter adapter) { producers.put(namespace, adapter); } - void addConsumerAdapter(String namespace, TestMsbConsumerAdapter adapter) { + synchronized void addConsumerAdapter(String namespace, TestMsbConsumerAdapter adapter) { consumers.put(namespace, adapter); } - void addPublishedTestMessage(String namespace, String jsonMessage) { + synchronized void addPublishedTestMessage(String namespace, String jsonMessage) { List publishedMessages = this.publishedMessages.get(namespace); if (publishedMessages == null) { publishedMessages = new ArrayList<>(); @@ -69,7 +69,7 @@ void addPublishedTestMessage(String namespace, String jsonMessage) { /** * Reset the storage. */ - public void cleanup() { + public synchronized void cleanup() { consumers.clear(); producers.clear(); publishedMessages.clear(); @@ -80,7 +80,7 @@ public void cleanup() { * @param namespace * @param jsonMessage */ - public void publishIncomingMessage(String namespace, String jsonMessage) { + public synchronized void publishIncomingMessage(String namespace, String jsonMessage) { TestMsbConsumerAdapter consumerAdapter = consumers.get(namespace); if(consumerAdapter != null) { consumerAdapter.pushTestMessage(jsonMessage); @@ -92,7 +92,7 @@ public void publishIncomingMessage(String namespace, String jsonMessage) { * @param namespace * @return */ - public List getOutgoingMessages(String namespace) { + public synchronized List getOutgoingMessages(String namespace) { List publishedMessages = this.publishedMessages.get(namespace); if (publishedMessages == null) { publishedMessages = Collections.emptyList(); @@ -105,7 +105,7 @@ public List getOutgoingMessages(String namespace) { * @param namespace * @return */ - public String getOutgoingMessage(String namespace) { + public synchronized String getOutgoingMessage(String namespace) { List publishedMessages = this.publishedMessages.get(namespace); if (publishedMessages == null || publishedMessages.size() != 1) { throw new RuntimeException("A single outgoing message is expected"); diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java index 07ff48ed..61aa6dd3 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java @@ -16,11 +16,11 @@ public static TestMsbStorageForObjectFactory extract(MsbContext msbContext) { return ((TestMsbObjectFactory)msbContext.getObjectFactory()).getStorage(); } - void addCapture(RequesterCapture requesterCapture) { + synchronized void addCapture(RequesterCapture requesterCapture) { requesters.put(requesterCapture.getNamespace(), requesterCapture); } - void addCapture(ResponderCapture responderCapture) { + synchronized void addCapture(ResponderCapture responderCapture) { responders.put(responderCapture.getNamespace(), responderCapture); } @@ -28,7 +28,7 @@ void addCapture(ResponderCapture responderCapture) { /** * Reset the storage. */ - public void cleanup() { + public synchronized void cleanup() { requesters.clear(); responders.clear(); } @@ -39,7 +39,7 @@ public void cleanup() { * @param * @return */ - public RequesterCapture getRequesterCapture(String namespace) { + public synchronized RequesterCapture getRequesterCapture(String namespace) { return (RequesterCapture) requesters.get(namespace); } @@ -49,7 +49,7 @@ public RequesterCapture getRequesterCapture(String namespace) { * @param * @return */ - public ResponderCapture getResponderCapture(String namespace) { + public synchronized ResponderCapture getResponderCapture(String namespace) { return (ResponderCapture) responders.get(namespace); } } From d3a065a32d6f442a964ab6446d2b4c341ce0829f Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 15 Feb 2016 17:44:32 +0200 Subject: [PATCH 079/226] additional testing and test coverage exclusions --- .coveragerc | 4 +++ .../amqp/AmqpConsumerAdapterTest.java | 27 ++++++++++++++++--- .../AcknowledgementHandlerTest.java | 12 ++------- 3 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 .coveragerc rename amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java => core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerTest.java (96%) diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..2609589e --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[run] +omit = acceptance/* + doc/* + jmeter/* diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java index 044db397..036c893f 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java @@ -6,16 +6,15 @@ import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; + import io.github.tcdl.msb.adapters.ConsumerAdapter; +import io.github.tcdl.msb.api.exception.ChannelException; import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; import java.io.IOException; import java.nio.charset.Charset; import java.util.Optional; -import java.util.concurrent.ExecutorService; import org.junit.Before; import org.junit.Test; @@ -110,6 +109,15 @@ public void testSubscribeDurableQueueCreated() throws IOException { verify(mockChannel).queueBind("myTopic.myGroupId.d", "myTopic", ""); } + + @Test(expected = ChannelException.class) + public void testSubscribeException() throws IOException { + AmqpConsumerAdapter adapter = createAdapterWithDurableConf("myTopic", "myGroupId", false); + when(mockChannel.basicConsume(anyString(), anyBoolean(), any(AmqpMessageConsumer.class))) + .thenThrow(IOException.class); + adapter.subscribe((jsonMessage, ackHandler) -> {}); + } + @Test public void testRegisteredHandlerInvoked() throws IOException { AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", false); @@ -139,6 +147,17 @@ public void testUnsubscribe() throws IOException { verify(mockChannel).basicCancel(consumerTag); } + + @Test(expected = ChannelException.class) + public void testUnsubscribeException() throws IOException { + AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", false); + String consumerTag = "my consumer tag"; + when(mockChannel.basicConsume(anyString(), anyBoolean(), any(Consumer.class))) + .thenThrow(IOException.class); + adapter.subscribe((jsonMessage, ackHandler) -> {}); + adapter.unsubscribe(); + } + @Test public void testIsDurableFalseIfResponseTopicAndNonDurableConfig() throws IOException { boolean isResponseTopic = true; diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java b/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerTest.java similarity index 96% rename from amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java rename to core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerTest.java index 3657173e..03166376 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAcknowledgementHandlerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerTest.java @@ -1,13 +1,8 @@ -package io.github.tcdl.msb.adapters.amqp; +package io.github.tcdl.msb.acknowledge; import static org.junit.Assert.*; import static org.mockito.Mockito.*; - - import java.util.stream.IntStream; - -import io.github.tcdl.msb.acknowledge.AcknowledgementAdapter; -import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -16,16 +11,13 @@ @RunWith(MockitoJUnitRunner.class) -public class AmqpAcknowledgementHandlerTest { +public class AcknowledgementHandlerTest { private AcknowledgementHandlerImpl handler; @Mock private AcknowledgementAdapter acknowledgementAdapter; - private long deliveryTag = 123123123; - - @Before public void setUp() { handler = getHandler(false); From 9fd87b095a5fbb4536640c59088e68840b46955e Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 15 Feb 2016 18:00:00 +0200 Subject: [PATCH 080/226] coverage exclusions --- .coveragerc | 4 ---- pom.xml | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 2609589e..00000000 --- a/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] -omit = acceptance/* - doc/* - jmeter/* diff --git a/pom.xml b/pom.xml index 5fe26ef6..069004bf 100644 --- a/pom.xml +++ b/pom.xml @@ -243,6 +243,9 @@ **/examples/* + **/acceptance/* + **/doc/* + **/jmeter/* From 5038154fee47606547a8568118bb1c6789f67e70 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 16 Feb 2016 13:10:17 +0200 Subject: [PATCH 081/226] Minor fix in Topics#toString --- core/src/main/java/io/github/tcdl/msb/api/message/Topics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java b/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java index daeee5db..6bd41014 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java +++ b/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java @@ -32,6 +32,6 @@ public String getForward() { @Override public String toString() { - return "Topics [to=" + to + ", response=" + response + ", forward=" + response + "]"; + return "Topics [to=" + to + ", response=" + response + ", forward=" + forward + "]"; } } From 7099ef682752bbefd675fa3725f0ca4a792005e9 Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 16 Feb 2016 13:11:03 +0200 Subject: [PATCH 082/226] additional testing, minor cleanup and fixes --- .../amqp/AmqpConsumerAdapterTest.java | 1 - .../DuplicateCollectorException.java | 10 ----- .../exception/MessageBuilderException.java | 11 ----- .../github/tcdl/msb/collector/Collector.java | 16 +++---- .../io/github/tcdl/msb/support/Utils.java | 3 +- ...hutdownScheduledExecutorDecoratorTest.java | 32 +++++++++---- .../tcdl/msb/collector/CollectorTest.java | 45 +++++++++++++++++++ .../msb/collector/TimeoutManagerTest.java | 16 +++++++ .../tcdl/msb/impl/RequesterImplTest.java | 24 +++++----- .../io/github/tcdl/msb/support/UtilsTest.java | 45 +++++++++++++++++-- 10 files changed, 148 insertions(+), 55 deletions(-) delete mode 100644 core/src/main/java/io/github/tcdl/msb/api/exception/DuplicateCollectorException.java delete mode 100644 core/src/main/java/io/github/tcdl/msb/api/exception/MessageBuilderException.java diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java index 036c893f..0b5e17f2 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java @@ -147,7 +147,6 @@ public void testUnsubscribe() throws IOException { verify(mockChannel).basicCancel(consumerTag); } - @Test(expected = ChannelException.class) public void testUnsubscribeException() throws IOException { AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", false); diff --git a/core/src/main/java/io/github/tcdl/msb/api/exception/DuplicateCollectorException.java b/core/src/main/java/io/github/tcdl/msb/api/exception/DuplicateCollectorException.java deleted file mode 100644 index 9ebae2eb..00000000 --- a/core/src/main/java/io/github/tcdl/msb/api/exception/DuplicateCollectorException.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.tcdl.msb.api.exception; - -/** - * Exception is thrown in case more then one Collector is created per correlationId. - */ -public class DuplicateCollectorException extends MsbException { - public DuplicateCollectorException(String message) { - super(message); - } -} diff --git a/core/src/main/java/io/github/tcdl/msb/api/exception/MessageBuilderException.java b/core/src/main/java/io/github/tcdl/msb/api/exception/MessageBuilderException.java deleted file mode 100644 index f5b8ac3a..00000000 --- a/core/src/main/java/io/github/tcdl/msb/api/exception/MessageBuilderException.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.tcdl.msb.api.exception; - -/** - * Wraps any exception thrown while converting Java object to/from during building {@link io.github.tcdl.msb.api.message.Message}. - */ -public class MessageBuilderException extends MsbException { - - public MessageBuilderException(String message) { - super(message); - } -} diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index fe410f67..9c8060fc 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -126,17 +126,11 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt this.payloadTypeReference = payloadTypeReference; - if (eventHandlers != null) { - onRawResponse = Optional.ofNullable(eventHandlers.onRawResponse()); - onResponse = Optional.ofNullable(eventHandlers.onResponse()); - onAcknowledge = Optional.ofNullable(eventHandlers.onAcknowledge()); - onEnd = Optional.ofNullable(eventHandlers.onEnd()); - } else { - onRawResponse = Optional.empty(); - onResponse = Optional.empty(); - onAcknowledge = Optional.empty(); - onEnd = Optional.empty(); - } + onRawResponse = Optional.ofNullable(eventHandlers.onRawResponse()); + onResponse = Optional.ofNullable(eventHandlers.onResponse()); + onAcknowledge = Optional.ofNullable(eventHandlers.onAcknowledge()); + onEnd = Optional.ofNullable(eventHandlers.onEnd()); + } @Override diff --git a/core/src/main/java/io/github/tcdl/msb/support/Utils.java b/core/src/main/java/io/github/tcdl/msb/support/Utils.java index 25f4b051..fc9b20c6 100644 --- a/core/src/main/java/io/github/tcdl/msb/support/Utils.java +++ b/core/src/main/java/io/github/tcdl/msb/support/Utils.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.NullNode; import io.github.tcdl.msb.api.exception.JsonConversionException; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,7 +75,7 @@ public Type getType() { } public static T fromJson(String json, TypeReference typeReference, ObjectMapper objectMapper) { - if (json == null) + if (StringUtils.isEmpty(json)) return null; try { return objectMapper.readValue(json, typeReference); diff --git a/core/src/test/java/io/github/tcdl/msb/RunOnShutdownScheduledExecutorDecoratorTest.java b/core/src/test/java/io/github/tcdl/msb/RunOnShutdownScheduledExecutorDecoratorTest.java index 8aeed975..454b070b 100644 --- a/core/src/test/java/io/github/tcdl/msb/RunOnShutdownScheduledExecutorDecoratorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/RunOnShutdownScheduledExecutorDecoratorTest.java @@ -6,15 +6,13 @@ import org.junit.Test; import org.mockito.Mockito; +import java.util.concurrent.CancellationException; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; public class RunOnShutdownScheduledExecutorDecoratorTest { @@ -56,18 +54,36 @@ public void testShutdownWithCancelledTask() { } @Test(timeout = 20000) - public void testShutdownWithCompletedTask() { + public void testShutdownWithCompletedTask() throws Exception { Runnable mockCompletedRunnable = mock(Runnable.class); - executorDecorator.schedule(mockCompletedRunnable, TIME_IMMEDIATE, TimeUnit.SECONDS); + ScheduledFuture scheduleCompleted = executorDecorator.schedule(mockCompletedRunnable, TIME_IMMEDIATE, TimeUnit.SECONDS); verify(mockCompletedRunnable, timeout(1000).times(1)).run(); Runnable mockRunnable = mock(Runnable.class); - executorDecorator.schedule(mockRunnable, TIME_FAR_FUTURE, TimeUnit.SECONDS); + ScheduledFuture scheduleFuture =executorDecorator.schedule(mockRunnable, TIME_FAR_FUTURE, TimeUnit.SECONDS); + + assertFalse(scheduleCompleted.isCancelled()); + assertTrue(scheduleCompleted.isDone()); + + assertFalse(scheduleFuture.isCancelled()); + assertFalse(scheduleFuture.isDone()); + + assertNull(scheduleCompleted.get()); executorDecorator.shutdown(); verify(mockRunnable, times(1)).run(); verify(mockCompletedRunnable, times(1)).run(); // verify the task is not invoked again + + assertTrue(scheduleFuture.isCancelled()); + assertTrue(scheduleFuture.isDone()); + + try { + scheduleFuture.get(); + fail("CancellationException is expected"); + } catch (CancellationException ex) { + //ok + } } @Test(timeout = 20000) diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java index 7ec0b2c0..6eefc704 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java @@ -31,6 +31,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; import java.util.function.BiConsumer; +import java.util.stream.IntStream; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -1134,6 +1136,32 @@ public void testWaitForResponsesReceivedGreaterTimeout() throws InterruptedExcep assertThat(timeoutCaptor.getValue()).isBetween(1, timeoutLeftToWait); } + @Test + public void testMultipleAckTimeouts() throws InterruptedException { + Collector collector = createCollector(); + + int numAckSequenceRepeats = 5; + int numAckTimeouts = 4; + int numAskTimeoutRepeats = 3; + + IntStream.range(0, numAckSequenceRepeats).forEach((i)->{ + IntStream.range(0, numAckTimeouts).forEach((j)->{ + Acknowledge ack = new Acknowledge + .Builder() + .withResponderId("tc") + .withTimeoutMs(1000 + j * 700) + .build(); + IntStream.range(0, numAskTimeoutRepeats).forEach((k)->{ + collector.processAck(ack); + }); + }); + }); + + collector.waitForResponses(); + // call enableResponseTimeout() on waitForResponses() call and on each timeout change + verify(timeoutManagerMock, times(numAckTimeouts * numAckSequenceRepeats + 1)).enableResponseTimeout(anyInt(), any()); + } + @Test public void testWaitForAcks() { int timeoutMs = 1000; @@ -1149,6 +1177,23 @@ public void testWaitForAcks() { assertThat(timeoutCaptor.getValue()).isBetween(500, timeoutMs); } + @Test + public void testWaitForAcksMultipleInvocations() { + int timeoutMs = 1000; + + when(requestOptionsMock.getAckTimeout()).thenReturn(timeoutMs); + Collector collector = createCollector(); + + when(timeoutManagerMock.enableAckTimeout(anyInt(), any())).thenReturn(mock(ScheduledFuture.class)); + + collector.listenForResponses(); + collector.waitForAcks(); + collector.waitForAcks(); + collector.waitForAcks(); + + verify(timeoutManagerMock, times(1)).enableAckTimeout(anyInt(), any()); + } + private Collector createCollector() { return new Collector(TOPIC, originalMessage, requestOptionsMock, msbContext, eventHandlers, new TypeReference() { }) { diff --git a/core/src/test/java/io/github/tcdl/msb/collector/TimeoutManagerTest.java b/core/src/test/java/io/github/tcdl/msb/collector/TimeoutManagerTest.java index 9956c007..5d7c5412 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/TimeoutManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/TimeoutManagerTest.java @@ -29,6 +29,14 @@ public void testEnableResponseTimeout() { verify(mockCollector, timeout(50)).end(); } + @Test + public void testEnableResponseTimeoutRejected() { + TimeoutManager timeoutManager = new TimeoutManager(1); + timeoutManager.shutdown(); + timeoutManager.enableResponseTimeout(10, mockCollector); + verify(mockCollector, never()).end(); + } + @Test public void testEnableResponseTimeoutMultipleTimesLastWin() { TimeoutManager timeoutManager = new TimeoutManager(2); @@ -46,6 +54,14 @@ public void testEnableAckTimeoutPositive() { verify(mockCollector, timeout(50)).end(); } + @Test + public void testEnableAckTimeoutRejected() { + TimeoutManager timeoutManager = new TimeoutManager(1); + timeoutManager.shutdown(); + timeoutManager.enableAckTimeout(10, mockCollector); + verify(mockCollector, never()).end(); + } + @Test public void testEnableAckTimeoutNegative() { TimeoutManager timeoutManager = new TimeoutManager(1); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index 6a9651dd..f324c356 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -5,12 +5,8 @@ import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; + import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.Consumer; import io.github.tcdl.msb.Producer; @@ -58,7 +54,7 @@ public class RequesterImplTest { public void testPublishNoWaitForResponses() throws Exception { RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null); - requester.publish(TestUtils.createSimpleRequestPayload()); + publishByAllMethods(requester); verify(collectorMock, never()).listenForResponses(); verify(collectorMock, never()).waitForResponses(); @@ -68,10 +64,18 @@ public void testPublishNoWaitForResponses() throws Exception { public void testPublishWaitForResponses() throws Exception { RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null); - requester.publish(TestUtils.createSimpleRequestPayload()); + publishByAllMethods(requester); - verify(collectorMock).listenForResponses(); - verify(collectorMock).waitForResponses(); + verify(collectorMock, times(4)).listenForResponses(); + verify(collectorMock, times(4)).waitForResponses(); + } + + private void publishByAllMethods(RequesterImpl requester) { + Message originalMessage = TestUtils.createMsbRequestMessage("some:topic", "body text"); + requester.publish(TestUtils.createSimpleRequestPayload()); + requester.publish(TestUtils.createSimpleRequestPayload(), "tag", "anotherTag"); + requester.publish(TestUtils.createSimpleRequestPayload(), originalMessage); + requester.publish(TestUtils.createSimpleRequestPayload(), originalMessage, "tag", "anotherTag"); } @Test diff --git a/core/src/test/java/io/github/tcdl/msb/support/UtilsTest.java b/core/src/test/java/io/github/tcdl/msb/support/UtilsTest.java index 35ee33e9..05aa1ecf 100644 --- a/core/src/test/java/io/github/tcdl/msb/support/UtilsTest.java +++ b/core/src/test/java/io/github/tcdl/msb/support/UtilsTest.java @@ -2,17 +2,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.api.exception.JsonConversionException; +import org.junit.Ignore; import org.junit.Test; import java.time.Instant; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; import static io.github.tcdl.msb.support.Utils.TOPIC_ANNOUNCE; import static io.github.tcdl.msb.support.Utils.TOPIC_HEARTBEAT; import static io.github.tcdl.msb.support.Utils.isServiceTopic; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; public class UtilsTest { @Test @@ -54,6 +56,20 @@ public void testJsonDeserializationWithDefaultMapper() throws Exception { assertEquals("value", bean.getField()); } + @Test + public void testJsonDeserializationFromNull() throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + SimpleBean bean = Utils.fromJson(null, SimpleBean.class, objectMapper); + assertNull(bean); + } + + @Test + public void testJsonDeserializationFromEmptyString() throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + SimpleBean bean = Utils.fromJson("", SimpleBean.class, objectMapper); + assertNull(bean); + } + @Test public void testConvert() { int VALUE = 10; @@ -76,4 +92,27 @@ public void testConversionError() { Utils.convert(pojo, Integer.class, objectMapper); } + @Test + public void testGracefulShutdown() throws Exception { + ExecutorService executorService = mock(ExecutorService.class); + when(executorService.awaitTermination(anyInt(), eq(TimeUnit.SECONDS))) + .thenReturn(false, false, true); + Utils.gracefulShutdown(executorService, "any"); + verify(executorService, times(1)).shutdown(); + verify(executorService, times(3)).awaitTermination(anyInt(), eq(TimeUnit.SECONDS)); + + } + + @Test + @Ignore + public void testGracefulShutdownInterrupted() throws Exception { + ExecutorService executorService = mock(ExecutorService.class); + doThrow(InterruptedException.class) + .when(executorService) + .awaitTermination(anyInt(), eq(TimeUnit.SECONDS)); + Utils.gracefulShutdown(executorService, "any"); + verify(executorService, times(1)).shutdown(); + verify(executorService, times(1)).awaitTermination(anyInt(), eq(TimeUnit.SECONDS)); + } + } \ No newline at end of file From b1ecb3d5de1cfa0092dd23c7825854dea89aa3ad Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 22 Feb 2016 14:33:44 +0200 Subject: [PATCH 083/226] javadoc fix --- .../mock/adapterfactory/TestMsbStorageForAdapterFactory.java | 2 +- .../msb/mock/objectfactory/TestMsbStorageForObjectFactory.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java index 7b50a389..63eb41a9 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java @@ -10,7 +10,7 @@ import java.util.List; /** - * This class provides statics-based storage and accessors for {@link TestMsbAdapterFactory}-based + * This class provides storage and accessors for {@link TestMsbAdapterFactory}-based * testing. */ public class TestMsbStorageForAdapterFactory { diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java index 61aa6dd3..fe808df4 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java @@ -5,7 +5,7 @@ import java.util.Map; /** - * This class provides statics-based storage and accessors for {@link TestMsbObjectFactory}-based + * This class provides storage and accessors for {@link TestMsbObjectFactory}-based * testing. */ public class TestMsbStorageForObjectFactory { From 3346b23e9f75787e957c3d2165b4d919a688588c Mon Sep 17 00:00:00 2001 From: anha1 Date: Mon, 22 Feb 2016 14:47:59 +0200 Subject: [PATCH 084/226] ignored test fix --- core/src/test/java/io/github/tcdl/msb/support/UtilsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/test/java/io/github/tcdl/msb/support/UtilsTest.java b/core/src/test/java/io/github/tcdl/msb/support/UtilsTest.java index 05aa1ecf..bebbffa6 100644 --- a/core/src/test/java/io/github/tcdl/msb/support/UtilsTest.java +++ b/core/src/test/java/io/github/tcdl/msb/support/UtilsTest.java @@ -104,7 +104,6 @@ public void testGracefulShutdown() throws Exception { } @Test - @Ignore public void testGracefulShutdownInterrupted() throws Exception { ExecutorService executorService = mock(ExecutorService.class); doThrow(InterruptedException.class) From f25a1e5ce89fc424d8d05c40cd42e0bd3910cc15 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Tue, 1 Mar 2016 11:31:48 +0200 Subject: [PATCH 085/226] Updated travis secure --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e7ec4011..a93f83d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,5 @@ after_success: - mvn clean test jacoco:report coveralls:report env: global: - - secure: QRvQihs4BrO9UloFkafR0sUKkGI1pD2CY50SqN7mC+1iIkkrYRNpJpaUhxsZXTJFvo6sf9tOiZpe7mokAfFT72VLDpTCXCnN8GzIS0ozPfh7CjE/CdhfjZahxdMsz5x8Imsuo0kbB31r5j3uwJdc+zLr9lW5NRY5mpQbsZJIL+s= - - secure: j6r6rhtdRGVDVQf3CBCsB9Hrx/lpW6SRBznpryarkQS7e5gNy/Ftp+2bwxjYSvXTVovm1lSAOfLE3/EiQdo6BVgzSMftExR5R8pD20zNzUqje+wC9ANW0K9GfyeimFPR/I106tmUlMI/LiaC7xVt0dQlvlruMEejqwz1tLTMAw8= - - secure: kIjP9JhBP19mOgfvPJA0WbLhzr69/WV1gpxpFYCrfmyBQI6vFWnnsYrUrkLka9APpBUh3sgYARO71xUpFkmDoXadRHrpOi0FESDVNsRG5oKcBaCO3yWPALeptA+mqu6ofIHFMmjGU8BvHO7TsyI9bpjrYcSGC1xLe5ADbDZNESY= + - secure: GZTdP3ij3zpx4isJ4IH6baurpkt8jM/v109YsNxFxXPvkk6Cv2y3ohxeyINo/5c5O9skpf4rII5BXpMd9DqYhtQebIKK4tdvoGZbMUcq3dfk+o4LkoJKGtYqgFACooCQ7Z4IDTsW/i+jWCOnptcV+JHAb5FFO6kIR8xfqPiGMpY= + - secure: juuiB0BqUWLCPFQEol+j6Ulk/bv4lii8mAUIaiUOs7xfbqeFHsrP1jp5WONwjm2Gq2PrpMl42G2MVa6VhNm0l2EqgF9SRinxhEH9/Hu+OqCRWks7CCgZhdZ+DzDCdbIjsLwf+BR/nwIXOgVU8N6ZsbU1h6FoWZwqMbty/SBLgYs= From 1de9264c0f7fffe34dbf9f555a123da0fa9e5e91 Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 1 Mar 2016 13:03:15 +0200 Subject: [PATCH 086/226] performance improvements --- .../msb/adapters/amqp/AmqpAdapterFactory.java | 4 ++-- .../amqp/AmqpAutoRecoveringChannel.java | 4 ++-- .../adapters/amqp/AmqpMessageConsumer.java | 14 ++++++------- .../AmqpMessageHandlerInvokeStrategy.java | 4 ++-- .../amqp/AmqpMessageProcessingTask.java | 6 +++--- amqp/src/main/resources/amqp.conf | 2 +- ...nOnShutdownScheduledExecutorDecorator.java | 6 +++--- .../AcknowledgementHandlerImpl.java | 20 +++++++++---------- .../tcdl/msb/collector/CollectorManager.java | 19 +++++++----------- .../DefaultChannelMonitorAggregator.java | 12 +++++------ .../tcdl/msb/support/JsonValidator.java | 16 +++++++-------- .../io/github/tcdl/msb/support/Utils.java | 8 ++++---- .../msb/collector/CollectorManagerTest.java | 11 ++++------ examples/src/main/resources/logback.xml | 15 ++++++++++++++ 14 files changed, 74 insertions(+), 67 deletions(-) create mode 100644 examples/src/main/resources/logback.xml diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java index c2274ca7..9f8300fd 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java @@ -124,8 +124,8 @@ protected AmqpConnectionManager createConnectionManager(Connection connection) { */ protected Connection createConnection(ConnectionFactory connectionFactory) { try { - LOG.info(String.format("Opening AMQP connection to host = %s, port = %s, username = %s, password = xxx, virtualHost = %s...", - connectionFactory.getHost(), connectionFactory.getPort(), connectionFactory.getUsername(), connectionFactory.getVirtualHost())); + LOG.info("Opening AMQP connection to host = {}, port = {}, username = {}, password = xxx, virtualHost = {}...", + connectionFactory.getHost(), connectionFactory.getPort(), connectionFactory.getUsername(), connectionFactory.getVirtualHost()); Connection connection = connectionFactory.newConnection(); if (connection instanceof Recoverable) { // This cast is possible for connections created by a factory that supports auto-recovery diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java index c434cf61..b4e2e20a 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java @@ -58,12 +58,12 @@ private void createChannelForPublisherConfirms(AmqpConnectionManager connectionM channel.addConfirmListener(new ConfirmListener() { @Override public void handleAck(long deliveryTag, boolean multiple) throws IOException { - LOG.debug(String.format("Processing publisher ack (deliveryTag = %s, multiple = %b)", deliveryTag, multiple)); + LOG.debug("Processing publisher ack (deliveryTag = {}, multiple = {})", deliveryTag, multiple); } @Override public void handleNack(long deliveryTag, boolean multiple) throws IOException { - LOG.debug(String.format("Processing publisher nack (deliveryTag = %s, multiple = %b)", deliveryTag, multiple)); + LOG.debug("Processing publisher nack (deliveryTag = {}, multiple = {})", deliveryTag, multiple); } }); diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java index cfb417be..660bf2b1 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java @@ -43,27 +43,27 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp String bodyStr = new String(body, charset); - LOG.debug(String.format("[consumer tag: %s] Message consumed from broker: %s", consumerTag, bodyStr)); + LOG.debug("[consumer tag: {}] Message consumed from broker: {}", consumerTag, bodyStr); try { msgHandler.onMessage(bodyStr, ackHandler); - LOG.debug(String.format("[consumer tag: %s] Raw message has been handled: %s.", - consumerTag, bodyStr)); + LOG.debug("[consumer tag: {}] Raw message has been handled: {}.", + consumerTag, bodyStr); } catch (Exception e) { - LOG.error(String.format("[consumer tag: %s] Can't handle a raw message: %s.", - consumerTag, bodyStr), e); + LOG.error("[consumer tag: {}] Can't handle a raw message: {}.", + consumerTag, bodyStr, e); throw e; } } catch (Exception e) { // Catch all exceptions to prevent AMQP channel to be closed - LOG.error(String.format("[consumer tag: %s] Got exception while processing incoming message. About to send AMQP reject...", consumerTag), e); + LOG.error("[consumer tag: {}] Got exception while processing incoming message. About to send AMQP reject...", consumerTag, e); ackHandler.autoReject(); } } AcknowledgementHandlerInternal createAcknowledgementHandler(Channel channel, String consumerTag, long deliveryTag, boolean isRequeueRejectedMessages) { AmqpAcknowledgementAdapter adapter = new AmqpAcknowledgementAdapter(channel, consumerTag, deliveryTag); - String messageTextIdentifier = String.format("consumer tag: %s", consumerTag); + String messageTextIdentifier = "consumer tag: " + consumerTag; return new AcknowledgementHandlerImpl(adapter, isRequeueRejectedMessages, messageTextIdentifier); } diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java index 428569b6..ed0cc827 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java @@ -29,7 +29,7 @@ public AmqpMessageHandlerInvokeStrategy(ExecutorService consumerThreadPool) { @Override public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) { consumerThreadPool.submit(new AmqpMessageProcessingTask(messageHandler, message, acknowledgeHandler)); - LOG.debug(String.format("[correlation id: %s] Message has been put in the processing queue.", - message.getCorrelationId())); + LOG.debug("[correlation id: {}] Message has been put in the processing queue.", + message.getCorrelationId()); } } diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java index 8a4176b2..5c172d28 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java @@ -41,12 +41,12 @@ public void run() { MDC.setContextMap(mdcLogContextMap); } try { - LOG.debug(String.format("[correlation id: %s] Starting message processing", message.getCorrelationId())); + LOG.debug("[correlation id: {}] Starting message processing", message.getCorrelationId()); messageHandler.handleMessage(message, ackHandler); - LOG.debug(String.format("[correlation id: %s] Message has been processed", message.getCorrelationId())); + LOG.debug("[correlation id: {}] Message has been processed", message.getCorrelationId()); ackHandler.autoConfirm(); } catch (Exception e) { - LOG.error(String.format("[correlation id: %s] Failed to process message", message.getCorrelationId()), e); + LOG.error("[correlation id: {}] Failed to process message", message.getCorrelationId(), e); ackHandler.autoRetry(); } finally { if(mdcLogCopy) { diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf index f3e748be..0a24bb50 100644 --- a/amqp/src/main/resources/amqp.conf +++ b/amqp/src/main/resources/amqp.conf @@ -20,7 +20,7 @@ config.amqp = { consumerThreadPoolQueueCapacity = -1 # Interval of the heartbeats that are used to detect broken connections. Zero for none. See for more details: https://www.rabbitmq.com/heartbeats.html - heartbeatIntervalSec = 1 + heartbeatIntervalSec = 30 # Interval of connection recovery attempts. See for more details: https://www.rabbitmq.com/api-guide.html#connection-recovery networkRecoveryIntervalMs = 5000 diff --git a/core/src/main/java/io/github/tcdl/msb/RunOnShutdownScheduledExecutorDecorator.java b/core/src/main/java/io/github/tcdl/msb/RunOnShutdownScheduledExecutorDecorator.java index 874cd50e..931e6365 100644 --- a/core/src/main/java/io/github/tcdl/msb/RunOnShutdownScheduledExecutorDecorator.java +++ b/core/src/main/java/io/github/tcdl/msb/RunOnShutdownScheduledExecutorDecorator.java @@ -27,7 +27,7 @@ public class RunOnShutdownScheduledExecutorDecorator { private String name; public RunOnShutdownScheduledExecutorDecorator(String name, int corePoolSize, ThreadFactory threadFactory) { - LOG.info(String.format("[scheduled thread pool decorator '%s'] Starting with %d threads ", name, corePoolSize)); + LOG.info("[scheduled thread pool decorator '{}'] Starting with {} threads ", name, corePoolSize); this.name = name; scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); @@ -57,10 +57,10 @@ public synchronized void shutdown() { So we we can assume that {@link #tasks} cannot be modified at this point */ - LOG.info(String.format("[scheduled thread pool decorator '%s'] Executing pending tasks...", name)); + LOG.info("[scheduled thread pool decorator '{}'] Executing pending tasks...", name); tasks.values().forEach(java.lang.Runnable::run); tasks.clear(); - LOG.info(String.format("[scheduled thread pool decorator '%s'] Completed pending tasks execution.", name)); + LOG.info("[scheduled thread pool decorator '{}'] Completed pending tasks execution.", name); } /** diff --git a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java index bad44110..678a13dc 100644 --- a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java @@ -13,7 +13,7 @@ */ public class AcknowledgementHandlerImpl implements AcknowledgementHandlerInternal { - private static final String ACK_WAS_ALREADY_SENT = "[%s] Acknowledgement was already sent during message processing."; + private static final String ACK_WAS_ALREADY_SENT = "[{}] Acknowledgement was already sent during message processing."; private static final Logger LOG = LoggerFactory.getLogger(AcknowledgementHandlerImpl.class); @@ -44,7 +44,7 @@ public void setAutoAcknowledgement(boolean autoAcknowledgement) { public void confirmMessage() { executeAck("confirm", () -> { acknowledgementAdapter.confirm(); - LOG.debug(String.format("[%s] A message was confirmed", messageTextIdentifier)); + LOG.debug("[{}] A message was confirmed", messageTextIdentifier); }); } @@ -53,10 +53,10 @@ public void retryMessage() { executeAck("requeue", () -> { if(!isMessageRedelivered) { acknowledgementAdapter.retry(); - LOG.debug(String.format("[%s] A message was rejected with requeue", messageTextIdentifier)); + LOG.debug("[{}] A message was rejected with requeue", messageTextIdentifier); } else { acknowledgementAdapter.reject(); - LOG.warn(String.format("[%s] Can't requeue message because it already was redelivered once, discarding it instead", messageTextIdentifier)); + LOG.warn("[{}] Can't requeue message because it already was redelivered once, discarding it instead", messageTextIdentifier); } }); } @@ -65,7 +65,7 @@ public void retryMessage() { public void rejectMessage() { executeAck("reject", () -> { acknowledgementAdapter.reject(); - LOG.debug(String.format("[%s] A message was discarded", messageTextIdentifier)); + LOG.debug("[{}] A message was discarded", messageTextIdentifier); }); } @@ -74,31 +74,31 @@ private void executeAck(String actionName, AckAction ackAction) { try { ackAction.perform(); } catch (Exception e) { - LOG.error(String.format("[%s] Got exception when trying to %s a message:", messageTextIdentifier, actionName), e); + LOG.error("[{}] Got exception when trying to {} a message:", messageTextIdentifier, actionName, e); } } else { - LOG.error(String.format(ACK_WAS_ALREADY_SENT, messageTextIdentifier)); + LOG.error(ACK_WAS_ALREADY_SENT, messageTextIdentifier); } } public void autoConfirm() { executeAutoAck(() -> { confirmMessage(); - LOG.debug(String.format("[%s] A message was automatically confirmed after message processing", messageTextIdentifier)); + LOG.debug("[{}] A message was automatically confirmed after message processing", messageTextIdentifier); }); } public void autoReject() { executeAutoAck(() -> { rejectMessage(); - LOG.debug(String.format("[%s] A message was automatically rejected due to error during message processing", messageTextIdentifier)); + LOG.debug("[{}] A message was automatically rejected due to error during message processing", messageTextIdentifier); }); } public void autoRetry() { executeAutoAck(() -> { retryMessage(); - LOG.debug(String.format("[%s] A message was automatically rejected (with a requeue attempt) due to error during message processing", messageTextIdentifier)); + LOG.debug("[{}] A message was automatically rejected (with a requeue attempt) due to error during message processing", messageTextIdentifier); }); } diff --git a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java index 0088f3f2..5599e5c4 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/CollectorManager.java @@ -53,26 +53,21 @@ public void registerCollector(Collector collector) { String correlationId = collector.getRequestMessage().getCorrelationId(); collectorsByCorrelationId.putIfAbsent(correlationId, collector); - synchronized (this) { - if (!isSubscribed) { - channelManager.subscribeForResponses(topic, this); - isSubscribed = true; + if(!isSubscribed) { + synchronized (this) { + if (!isSubscribed) { + channelManager.subscribeForResponses(topic, this); + isSubscribed = true; + } } } } /** - * Remove this collector from collector's map, if it is present. If map is empty (no more collectors await on consumer topic) unsubscribe from consumer. + * Remove this collector from collector's map, if it is present. */ public void unregisterCollector(Collector collector) { collectorsByCorrelationId.remove(collector.getRequestMessage().getCorrelationId()); - - synchronized (this) { - if (collectorsByCorrelationId.isEmpty() && isSubscribed) { - channelManager.unsubscribe(topic); - isSubscribed = false; - } - } } @Override diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java b/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java index 8d85d587..c33ead82 100644 --- a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java +++ b/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java @@ -53,7 +53,7 @@ public DefaultChannelMonitorAggregator(MsbContextImpl msbContext, ScheduledExecu @Override public void start(boolean activateHeartbeats, long heartbeatIntervalMs, int heartbeatTimeoutMs) { channelManager.subscribe(TOPIC_ANNOUNCE, this::onAnnounce); - LOG.debug(String.format("Subscribed to %s", TOPIC_ANNOUNCE)); + LOG.debug("Subscribed to {}", TOPIC_ANNOUNCE); if (activateHeartbeats) { Runnable heartbeatTask = new HeartbeatTask(heartbeatTimeoutMs, objectFactory, this::onHeartbeatResponses); @@ -72,7 +72,7 @@ public void stop() { } void onHeartbeatResponses(List heartbeatResponses) { - LOG.debug(String.format("Handling heartbeat responses %s...", heartbeatResponses)); + LOG.debug("Handling heartbeat responses {}...", heartbeatResponses); AggregatorStats aggregatorStats = new AggregatorStats(); boolean successfullyAggregatedAtLeastOne = false; for (Message msg : heartbeatResponses) { @@ -81,10 +81,10 @@ void onHeartbeatResponses(List heartbeatResponses) { } } if (successfullyAggregatedAtLeastOne) { - LOG.debug(String.format("Calling registered handler for heartbeat statistics %s...", masterAggregatorStats)); + LOG.debug("Calling registered handler for heartbeat statistics {}...", masterAggregatorStats); handler.call(aggregatorStats); masterAggregatorStats = aggregatorStats; - LOG.debug(String.format("Heartbeat responses processed")); + LOG.debug("Heartbeat responses processed"); } } @@ -94,9 +94,9 @@ void onAnnounce(Message announcementMessage, AcknowledgementHandler acknowledgeH boolean successfullyAggregated = aggregateInfo(masterAggregatorStats, announcementMessage); if (successfullyAggregated) { - LOG.debug(String.format("Calling registered handler for announcement statistics %s...", masterAggregatorStats)); + LOG.debug("Calling registered handler for announcement statistics {}...", masterAggregatorStats); handler.call(masterAggregatorStats); - LOG.debug(String.format("Announcement message processed")); + LOG.debug("Announcement message processed"); } } diff --git a/core/src/main/java/io/github/tcdl/msb/support/JsonValidator.java b/core/src/main/java/io/github/tcdl/msb/support/JsonValidator.java index cc7cdf05..ffc99b4e 100644 --- a/core/src/main/java/io/github/tcdl/msb/support/JsonValidator.java +++ b/core/src/main/java/io/github/tcdl/msb/support/JsonValidator.java @@ -19,7 +19,7 @@ */ public class JsonValidator { - private Map schemaCache = new ConcurrentHashMap<>(); + private Map schemaCache = new ConcurrentHashMap<>(); private JsonReader jsonReader; public JsonValidator() { @@ -40,17 +40,17 @@ public void validate(String json, String schema) { try { JsonNode jsonNode = jsonReader.read(json); - JsonNode jsonSchemaNode = schemaCache.computeIfAbsent(schema, s -> { + + JsonSchema jsonSchema = schemaCache.computeIfAbsent(schema, s -> { try { - return jsonReader.read(s); - } catch (IOException e) { - throw new JsonSchemaValidationException(String.format("Failed reading schema"), e); + JsonNode jsonSchemaNode = jsonReader.read(s); + JsonSchemaFactory factory = JsonSchemaFactory.byDefault(); + return factory.getJsonSchema(jsonSchemaNode); + } catch (Exception e) { + throw new JsonSchemaValidationException("Failed reading schema", e); } }); - JsonSchemaFactory factory = JsonSchemaFactory.byDefault(); - JsonSchema jsonSchema = factory.getJsonSchema(jsonSchemaNode); - ProcessingReport validationReport = jsonSchema.validate(jsonNode); if (!validationReport.isSuccess()) { diff --git a/core/src/main/java/io/github/tcdl/msb/support/Utils.java b/core/src/main/java/io/github/tcdl/msb/support/Utils.java index fc9b20c6..8ec2d622 100644 --- a/core/src/main/java/io/github/tcdl/msb/support/Utils.java +++ b/core/src/main/java/io/github/tcdl/msb/support/Utils.java @@ -119,15 +119,15 @@ public static boolean isPayloadPresent(JsonNode rawPayload) { public static void gracefulShutdown(ExecutorService executorService, String executorServiceName) { int pollingTimeout = 10; - LOG.info(String.format("[thread pool '%s'] Shutting down...", executorServiceName)); + LOG.info("[thread pool '{}'] Shutting down...", executorServiceName); executorService.shutdown(); try { while (!executorService.awaitTermination(pollingTimeout, TimeUnit.SECONDS)) { - LOG.info(String.format("[thread pool '%s'] Still has some tasks to complete. Waiting...", executorServiceName)); + LOG.info("[thread pool '{}'] Still has some tasks to complete. Waiting...", executorServiceName); } } catch (InterruptedException e) { - LOG.warn(String.format("[thread pool '%s'] Interrupted while waiting for termination", executorServiceName), e); + LOG.warn("[thread pool '{}'] Interrupted while waiting for termination", executorServiceName, e); } - LOG.info(String.format("[thread pool '%s'] Shut down complete.", executorServiceName)); + LOG.info("[thread pool '{}'] Shut down complete.", executorServiceName); } } diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java index 3fe45fe6..4445a726 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorManagerTest.java @@ -116,7 +116,7 @@ public void testUnregisterMoreCollectorsExist() { verify(channelManagerMock, never()).unsubscribe(TOPIC); collectorManager.unregisterCollector(secondCollectorMock); - verify(channelManagerMock).unsubscribe(TOPIC); + verify(channelManagerMock, never()).unsubscribe(TOPIC); } @Test @@ -131,7 +131,7 @@ public void testUnregisterLastCollector() { collectorManager.unregisterCollector(collectorMock); collectorManager.unregisterCollector(secondCollectorMock); - verify(channelManagerMock).unsubscribe(TOPIC); + verify(channelManagerMock, never()).unsubscribe(TOPIC); } @Test @@ -140,9 +140,6 @@ public void testUnregisterTheSameCollectorsMultiplyTimes() { collectorManager.registerCollector(collectorMock); collectorManager.unregisterCollector(collectorMock); - verify(channelManagerMock, times(1)).unsubscribe(TOPIC); - - reset(channelManagerMock); collectorManager.unregisterCollector(collectorMock); verify(channelManagerMock, never()).unsubscribe(TOPIC); } @@ -153,11 +150,11 @@ public void testRegisterCollectorAfterUnregisterLast() { collectorManager.registerCollector(collectorMock); collectorManager.unregisterCollector(collectorMock); - verify(channelManagerMock, times(1)).unsubscribe(TOPIC); + verify(channelManagerMock, never()).unsubscribe(TOPIC); reset(channelManagerMock); collectorManager.registerCollector(collectorMock); - verify(channelManagerMock, times(1)).subscribeForResponses(TOPIC, collectorManager); + verify(channelManagerMock, never()).subscribeForResponses(TOPIC, collectorManager); } } diff --git a/examples/src/main/resources/logback.xml b/examples/src/main/resources/logback.xml new file mode 100644 index 00000000..200166fb --- /dev/null +++ b/examples/src/main/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level %logger [%t] - %m%n + + + + + + + + + \ No newline at end of file From 13a1f49460d1362760d87ea7bb693f43b462b939 Mon Sep 17 00:00:00 2001 From: anha1 Date: Fri, 26 Feb 2016 09:52:16 +0200 Subject: [PATCH 087/226] MsbContext shutdown callbacks support --- .../io/github/tcdl/msb/api/MsbContext.java | 5 ++ .../tcdl/msb/api/MsbContextBuilder.java | 6 ++- .../tcdl/msb/callback/CallbackHandler.java | 13 +++++ .../msb/callback/CallbackHandlerBase.java | 25 ++++++++++ .../msb/callback/MutableCallbackHandler.java | 23 +++++++++ .../github/tcdl/msb/impl/MsbContextImpl.java | 46 +++++++++++------ .../callback/MutableCallbackHandlerTest.java | 49 +++++++++++++++++++ .../tcdl/msb/impl/MsbContextImplTest.java | 43 ++++++++++------ .../io/github/tcdl/msb/support/TestUtils.java | 3 +- 9 files changed, 181 insertions(+), 32 deletions(-) create mode 100644 core/src/main/java/io/github/tcdl/msb/callback/CallbackHandler.java create mode 100644 core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java create mode 100644 core/src/main/java/io/github/tcdl/msb/callback/MutableCallbackHandler.java create mode 100644 core/src/test/java/io/github/tcdl/msb/callback/MutableCallbackHandlerTest.java diff --git a/core/src/main/java/io/github/tcdl/msb/api/MsbContext.java b/core/src/main/java/io/github/tcdl/msb/api/MsbContext.java index 2099e78b..bfc96e35 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/MsbContext.java +++ b/core/src/main/java/io/github/tcdl/msb/api/MsbContext.java @@ -16,4 +16,9 @@ public interface MsbContext { */ void shutdown(); + /** + * Add a callback that will be invoked before MsbContext shutdown. + * @param shutdownCallback + */ + void addShutdownCallback(Runnable shutdownCallback); } diff --git a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java index 95b6b34c..b646339e 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java +++ b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java @@ -9,6 +9,7 @@ import com.typesafe.config.ConfigFactory; import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.api.exception.MsbException; +import io.github.tcdl.msb.callback.MutableCallbackHandler; import io.github.tcdl.msb.collector.CollectorManagerFactory; import io.github.tcdl.msb.collector.TimeoutManager; import io.github.tcdl.msb.config.MsbConfig; @@ -104,7 +105,10 @@ public MsbContext build() { TimeoutManager timeoutManager = new TimeoutManager(msbConfig.getTimerThreadPoolSize()); CollectorManagerFactory collectorManagerFactory = new CollectorManagerFactory(channelManager); - MsbContextImpl msbContext = new MsbContextImpl(msbConfig, messageFactory, channelManager, clock, timeoutManager, payloadMapper, collectorManagerFactory); + MsbContextImpl msbContext = new MsbContextImpl(msbConfig, messageFactory, channelManager, + clock, timeoutManager, + payloadMapper, collectorManagerFactory, + new MutableCallbackHandler()); if (enableChannelMonitorAgent) { DefaultChannelMonitorAgent.start(msbContext); diff --git a/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandler.java b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandler.java new file mode 100644 index 00000000..07bca3a3 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandler.java @@ -0,0 +1,13 @@ +package io.github.tcdl.msb.callback; + +/** + * Container for {@link Runnable} callbacks. + */ +public interface CallbackHandler { + + /** + * Run call callbacks within the container, catch and log expections if any. + */ + void runCallbacks(); + +} diff --git a/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java new file mode 100644 index 00000000..5b36ab3b --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java @@ -0,0 +1,25 @@ +package io.github.tcdl.msb.callback; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Set; + +public abstract class CallbackHandlerBase implements CallbackHandler { + + private static final Logger LOG = LoggerFactory.getLogger(CallbackHandlerBase.class); + + protected final Set callbacks = new HashSet<>(); + + public void runCallbacks() { + for(Runnable callback: callbacks) { + try { + callback.run(); + } catch (Exception ex) { + LOG.warn("Exception while trying to invoke callback", ex); + } + } + } + +} diff --git a/core/src/main/java/io/github/tcdl/msb/callback/MutableCallbackHandler.java b/core/src/main/java/io/github/tcdl/msb/callback/MutableCallbackHandler.java new file mode 100644 index 00000000..8c8bf018 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/callback/MutableCallbackHandler.java @@ -0,0 +1,23 @@ +package io.github.tcdl.msb.callback; + +/** + * Mutable {@link CallbackHandler} implementation. + */ +public class MutableCallbackHandler extends CallbackHandlerBase { + + /** + * Add a callback. + * @param runnable + */ + public void add(Runnable runnable) { + callbacks.add(runnable); + } + + /** + * Remove a callback. + * @param runnable + */ + public void remove(Runnable runnable) { + callbacks.remove(runnable); + } +} diff --git a/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java index b7c102fc..800df240 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java @@ -4,6 +4,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.api.MsbContext; import io.github.tcdl.msb.api.ObjectFactory; +import io.github.tcdl.msb.callback.MutableCallbackHandler; import io.github.tcdl.msb.collector.CollectorManager; import io.github.tcdl.msb.collector.CollectorManagerFactory; import io.github.tcdl.msb.collector.TimeoutManager; @@ -11,7 +12,6 @@ import io.github.tcdl.msb.message.MessageFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import java.time.Clock; /** @@ -21,20 +21,23 @@ public class MsbContextImpl implements MsbContext { private static final Logger LOG = LoggerFactory.getLogger(MsbContextImpl.class); - private MsbConfig msbConfig; - private ObjectFactory objectFactory; - private MessageFactory messageFactory; - private ChannelManager channelManager; - private Clock clock; - private TimeoutManager timeoutManager; - private ObjectMapper payloadMapper; - private CollectorManagerFactory collectorManagerFactory; + private final MsbConfig msbConfig; + private volatile ObjectFactory objectFactory; + private final MessageFactory messageFactory; + private final ChannelManager channelManager; + private final Clock clock; + private final TimeoutManager timeoutManager; + private final ObjectMapper payloadMapper; + private final CollectorManagerFactory collectorManagerFactory; + private final MutableCallbackHandler shutdownCallbackHandler; + private volatile boolean isShutdown = false; public MsbContextImpl(MsbConfig msbConfig, MessageFactory messageFactory, ChannelManager channelManager, Clock clock, TimeoutManager timeoutManager, ObjectMapper payloadMapper, - CollectorManagerFactory collectorManagerFactory) { + CollectorManagerFactory collectorManagerFactory, + MutableCallbackHandler shutdownCallbackHandler) { this.msbConfig = msbConfig; this.messageFactory = messageFactory; this.channelManager = channelManager; @@ -42,18 +45,25 @@ public MsbContextImpl(MsbConfig msbConfig, MessageFactory messageFactory, Channe this.timeoutManager = timeoutManager; this.payloadMapper = payloadMapper; this.collectorManagerFactory = collectorManagerFactory; + this.shutdownCallbackHandler = shutdownCallbackHandler; } /** * {@inheritDoc} */ @Override - public void shutdown() { - LOG.info("Shutting down MSB context..."); - objectFactory.shutdown(); - timeoutManager.shutdown(); - channelManager.shutdown(); - LOG.info("MSB context has been shut down."); + public synchronized void shutdown() { + if(!isShutdown) { + isShutdown = true; + LOG.info("Shutting down MSB context..."); + shutdownCallbackHandler.runCallbacks(); + objectFactory.shutdown(); + timeoutManager.shutdown(); + channelManager.shutdown(); + LOG.info("MSB context has been shut down."); + } else { + LOG.warn("Trying to shutdown MsbContext several times"); + } } /** @@ -117,4 +127,8 @@ public void setObjectFactory(ObjectFactory objectFactory) { this.objectFactory = objectFactory; } + @Override + public void addShutdownCallback(Runnable shutdownCallback) { + shutdownCallbackHandler.add(shutdownCallback); + } } diff --git a/core/src/test/java/io/github/tcdl/msb/callback/MutableCallbackHandlerTest.java b/core/src/test/java/io/github/tcdl/msb/callback/MutableCallbackHandlerTest.java new file mode 100644 index 00000000..71407429 --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/callback/MutableCallbackHandlerTest.java @@ -0,0 +1,49 @@ +package io.github.tcdl.msb.callback; + +import org.assertj.core.api.exception.RuntimeIOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class MutableCallbackHandlerTest { + + @Mock + private Runnable callback1; + + @Mock + private Runnable callback2; + + @Mock + private Runnable callback3; + + @InjectMocks + private MutableCallbackHandler handler; + + @Test + public void testSuccess() throws Exception { + + doThrow(RuntimeIOException.class).when(callback1).run(); + + handler.add(callback1); + handler.add(callback2); + handler.add(callback3); + + handler.runCallbacks(); + + handler.remove(callback2); + + handler.runCallbacks(); + + verify(callback1, times(2)).run(); + verify(callback2, times(1)).run(); + verify(callback3, times(2)).run(); + } + +} \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/impl/MsbContextImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/MsbContextImplTest.java index 51476f6a..62955b91 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/MsbContextImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/MsbContextImplTest.java @@ -1,28 +1,25 @@ package io.github.tcdl.msb.impl; -import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.api.ObjectFactory; +import io.github.tcdl.msb.callback.MutableCallbackHandler; import io.github.tcdl.msb.collector.CollectorManagerFactory; import io.github.tcdl.msb.collector.TimeoutManager; import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.message.MessageFactory; -import io.github.tcdl.msb.support.TestUtils; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import java.time.Clock; - import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class MsbContextImplTest { - private Clock clock = Clock.systemDefaultZone(); - @Mock private MsbConfig msbConfigurationsMock; @@ -41,23 +38,41 @@ public class MsbContextImplTest { @Mock private ObjectFactory objectFactoryMock; - private ObjectMapper messageMapper = TestUtils.createMessageMapper(); + @Mock + private MutableCallbackHandler shutdownCallbackHandlerMock; + + @Mock + private Runnable callbackMock; + + @InjectMocks + private MsbContextImpl msbContext; @Test public void testShutdown() { - MsbContextImpl msbContext = new MsbContextImpl(msbConfigurationsMock, messageFactoryMock, channelManagerMock, clock, - timeoutManagerMock, messageMapper, collectorManagerFactoryMock); msbContext.setObjectFactory(objectFactoryMock); msbContext.shutdown(); - verify(objectFactoryMock).shutdown(); - verify(timeoutManagerMock).shutdown(); - verify(channelManagerMock).shutdown(); + verifyShutdownOnce(); + + msbContext.shutdown(); + msbContext.shutdown(); + verifyShutdownOnce(); + } + + private void verifyShutdownOnce() { + verify(shutdownCallbackHandlerMock, times(1)).runCallbacks(); + verify(objectFactoryMock, times(1)).shutdown(); + verify(timeoutManagerMock, times(1)).shutdown(); + verify(channelManagerMock, times(1)).shutdown(); + } + + @Test + public void testAddShutdownCallback() { + msbContext.addShutdownCallback(callbackMock); + verify(shutdownCallbackHandlerMock).add(callbackMock); } @Test public void testSetObjectFactory() { - MsbContextImpl msbContext = new MsbContextImpl(msbConfigurationsMock, messageFactoryMock, channelManagerMock, clock, - timeoutManagerMock, messageMapper, collectorManagerFactoryMock); msbContext.setObjectFactory(objectFactoryMock); assertEquals(objectFactoryMock, msbContext.getObjectFactory()); } diff --git a/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java b/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java index 8eda5932..8b17e398 100644 --- a/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java +++ b/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java @@ -16,6 +16,7 @@ import io.github.tcdl.msb.api.message.MetaMessage; import io.github.tcdl.msb.api.message.Topics; import io.github.tcdl.msb.api.message.payload.RestPayload; +import io.github.tcdl.msb.callback.MutableCallbackHandler; import io.github.tcdl.msb.collector.CollectorManagerFactory; import io.github.tcdl.msb.collector.TimeoutManager; import io.github.tcdl.msb.config.MsbConfig; @@ -430,7 +431,7 @@ public MsbContextImpl build() { private static class TestMsbContext extends MsbContextImpl { TestMsbContext(MsbConfig msbConfig, MessageFactory messageFactory, ChannelManager channelManager, Clock clock, TimeoutManager timeoutManager, CollectorManagerFactory collectorManagerFactory) { - super(msbConfig, messageFactory, channelManager, clock, timeoutManager, createMessageMapper(), collectorManagerFactory); + super(msbConfig, messageFactory, channelManager, clock, timeoutManager, createMessageMapper(), collectorManagerFactory, new MutableCallbackHandler()); } public void setFactory(ObjectFactory objectFactory) { From 5b061f55efc20cee1cf3aac332533e72b14bb4d3 Mon Sep 17 00:00:00 2001 From: anha1 Date: Fri, 26 Feb 2016 09:59:18 +0200 Subject: [PATCH 088/226] MsbContext shutdown callbacks support --- .../io/github/tcdl/msb/callback/CallbackHandlerBase.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java index 5b36ab3b..b19dd745 100644 --- a/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java +++ b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java @@ -3,14 +3,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashSet; -import java.util.Set; +import java.util.ArrayList; +import java.util.List; public abstract class CallbackHandlerBase implements CallbackHandler { private static final Logger LOG = LoggerFactory.getLogger(CallbackHandlerBase.class); - protected final Set callbacks = new HashSet<>(); + protected final List callbacks = new ArrayList<>(); public void runCallbacks() { for(Runnable callback: callbacks) { From 9bef8254de1d756ef8e9f5aad2fbdf81ac5e12d6 Mon Sep 17 00:00:00 2001 From: anha1 Date: Fri, 26 Feb 2016 10:05:25 +0200 Subject: [PATCH 089/226] MsbContext shutdown callbacks support --- .../io/github/tcdl/msb/callback/CallbackHandlerBase.java | 6 +++--- .../tcdl/msb/callback/MutableCallbackHandlerTest.java | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java index b19dd745..265e0ecd 100644 --- a/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java +++ b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java @@ -3,14 +3,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; +import java.util.LinkedHashSet; +import java.util.Set; public abstract class CallbackHandlerBase implements CallbackHandler { private static final Logger LOG = LoggerFactory.getLogger(CallbackHandlerBase.class); - protected final List callbacks = new ArrayList<>(); + protected final Set callbacks = new LinkedHashSet<>(); public void runCallbacks() { for(Runnable callback: callbacks) { diff --git a/core/src/test/java/io/github/tcdl/msb/callback/MutableCallbackHandlerTest.java b/core/src/test/java/io/github/tcdl/msb/callback/MutableCallbackHandlerTest.java index 71407429..ea2a0853 100644 --- a/core/src/test/java/io/github/tcdl/msb/callback/MutableCallbackHandlerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/callback/MutableCallbackHandlerTest.java @@ -23,6 +23,9 @@ public class MutableCallbackHandlerTest { @Mock private Runnable callback3; + @Mock + private Runnable callback4; + @InjectMocks private MutableCallbackHandler handler; @@ -35,6 +38,10 @@ public void testSuccess() throws Exception { handler.add(callback2); handler.add(callback3); + for(int i=0; i < 11; i++) { + handler.add(callback4); + } + handler.runCallbacks(); handler.remove(callback2); @@ -44,6 +51,7 @@ public void testSuccess() throws Exception { verify(callback1, times(2)).run(); verify(callback2, times(1)).run(); verify(callback3, times(2)).run(); + verify(callback4, times(2)).run(); } } \ No newline at end of file From f5096934d14ecc26a2d0101b9fee6f5c8aa5e172 Mon Sep 17 00:00:00 2001 From: anha1 Date: Fri, 26 Feb 2016 14:56:38 +0200 Subject: [PATCH 090/226] MsbContext shutdown callbacks support --- .../main/java/io/github/tcdl/msb/impl/MsbContextImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java index 800df240..e5548169 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java @@ -30,7 +30,7 @@ public class MsbContextImpl implements MsbContext { private final ObjectMapper payloadMapper; private final CollectorManagerFactory collectorManagerFactory; private final MutableCallbackHandler shutdownCallbackHandler; - private volatile boolean isShutdown = false; + private volatile boolean isShutdownComplete = false; public MsbContextImpl(MsbConfig msbConfig, MessageFactory messageFactory, ChannelManager channelManager, Clock clock, @@ -53,8 +53,8 @@ public MsbContextImpl(MsbConfig msbConfig, MessageFactory messageFactory, Channe */ @Override public synchronized void shutdown() { - if(!isShutdown) { - isShutdown = true; + if(!isShutdownComplete) { + isShutdownComplete = true; LOG.info("Shutting down MSB context..."); shutdownCallbackHandler.runCallbacks(); objectFactory.shutdown(); From eedd9a6f013f2d89770e64c42e784bf0771c543a Mon Sep 17 00:00:00 2001 From: anha1 Date: Fri, 26 Feb 2016 17:26:51 +0200 Subject: [PATCH 091/226] MsbContext shutdown callbacks support - minor fixes --- .../main/java/io/github/tcdl/msb/callback/CallbackHandler.java | 2 +- .../java/io/github/tcdl/msb/callback/CallbackHandlerBase.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandler.java b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandler.java index 07bca3a3..1710e9f4 100644 --- a/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandler.java +++ b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandler.java @@ -6,7 +6,7 @@ public interface CallbackHandler { /** - * Run call callbacks within the container, catch and log expections if any. + * Run all callbacks within the container, catch and log exceptions if any. */ void runCallbacks(); diff --git a/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java index 265e0ecd..e0a6bf4b 100644 --- a/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java +++ b/core/src/main/java/io/github/tcdl/msb/callback/CallbackHandlerBase.java @@ -17,7 +17,7 @@ public void runCallbacks() { try { callback.run(); } catch (Exception ex) { - LOG.warn("Exception while trying to invoke callback", ex); + LOG.warn("Exception while trying to invoke a callback", ex); } } } From b576ba99bd7b6359ef58fc90c2f3ab9b17209842 Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 1 Mar 2016 16:07:23 +0200 Subject: [PATCH 092/226] schema validation disabled by default, release notes updates --- core/src/main/resources/reference.conf | 2 +- release-notes.html | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index e55faa79..0b4ded99 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -11,7 +11,7 @@ msbConfig { timerThreadPoolSize = 10 # Enable/disable message validation against json schema - validateMessage = true + validateMessage = false brokerAdapterFactory = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory" diff --git a/release-notes.html b/release-notes.html index 4841456c..bf245d44 100644 --- a/release-notes.html +++ b/release-notes.html @@ -4,13 +4,20 @@ -

Welcome to MSB-Java version 1.4.3

+

Welcome to MSB-Java version 1.4.4

February 09, 2016

-
MSB-Java version 1.4.3 contains minor improvements.
+
 
 ------------------------------------------------------------------------------------
+Features of MSB-Java version 1.4.4:
+   - MsbContext shutdown callbacks support;
+   - incoming messages schema validation is disabled by default;
+   - AMQP default heartbeat interval increased to 30s;
+   - performance improvements;
+   - MSB mocking support included, see MSB-TEST-MOCKING.md for details.
+
 Features of MSB-Java version 1.4.3:
    - Added logging support: all message tags concatenated into one string can be added to consumers thread
      MDC (see http://www.slf4j.org/api/org/slf4j/MDC.html) in 'msbTags' field. Message tags that have key-value format

From 3d46e91afedf1535ba70f664640bd97efdccbce4 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 4 Mar 2016 18:17:24 +0200
Subject: [PATCH 093/226] Updated travis secure

---
 .travis.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index a93f83d6..b3a5f39a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,5 +10,5 @@ after_success:
 - mvn clean test jacoco:report coveralls:report
 env:
   global:
-  - secure: GZTdP3ij3zpx4isJ4IH6baurpkt8jM/v109YsNxFxXPvkk6Cv2y3ohxeyINo/5c5O9skpf4rII5BXpMd9DqYhtQebIKK4tdvoGZbMUcq3dfk+o4LkoJKGtYqgFACooCQ7Z4IDTsW/i+jWCOnptcV+JHAb5FFO6kIR8xfqPiGMpY=
-  - secure: juuiB0BqUWLCPFQEol+j6Ulk/bv4lii8mAUIaiUOs7xfbqeFHsrP1jp5WONwjm2Gq2PrpMl42G2MVa6VhNm0l2EqgF9SRinxhEH9/Hu+OqCRWks7CCgZhdZ+DzDCdbIjsLwf+BR/nwIXOgVU8N6ZsbU1h6FoWZwqMbty/SBLgYs=
+  - secure: LIfUuBznv98TeC2mzZG6beSXgoyAtAnVdEkj+HfLR2Dm5wmqAC/LujAtFcqEL79c8+48/g4K3sM8OoUWyELdS8G9uYGgh6BLxpI+jAjpUoYGSlzfpM5Q6V5FG6oixkzBMMGV2tNDvH+0IcrhR7uPN1pMz8vvS01AqOlWXAD83Ms=
+  - secure: CBVCm/P1QMFd4B6jqdfJV5cwYeubIMcMyppS8PS//QL3OzaC9x+h5wWf2Rv7x4PItCOHPEy7kavIBJRAEel8H5egVVydkoNSvGglqBO5uQQvojl89VJGAfCp/7Evv9PP8Z9YhCIWPzQbr0kuL9+6amkUcIRxOLgcvQxrDvubqw8=

From 6525fe922a1ed3fdbc70830c55531f37d56c8a6d Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Thu, 10 Mar 2016 16:45:37 +0200
Subject: [PATCH 094/226] Updated 1.4.4 release date.

---
 release-notes.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/release-notes.html b/release-notes.html
index bf245d44..15409c77 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -6,7 +6,7 @@
 
 

Welcome to MSB-Java version 1.4.4

-

February 09, 2016

+

March 10, 2016

 
@@ -24,7 +24,7 @@ 

February 09, 2016

(e.g. 'requestId:123456' or 'requestId-123456') can be added to consumers thread MDC as separate properties where part before delimiter is a key and part after delimiter is a value. Delimiter is configurable. MDC is cleared after message handler invocation return; - - it is possible to define a messages forwarding namespace for requests; + - it is possible to define a messages forwarding namespace (added topics.forward field to the msb envelope); - Requester API now allows to publish message with multiple tags. Features of MSB-Java version 1.4.2: From 0d1acf9d8849b9ee942a6d8bfe8162bba8183064 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Thu, 10 Mar 2016 23:53:48 +0200 Subject: [PATCH 095/226] [maven-release-plugin] prepare release msb-java-1.4.4 --- acceptance/pom.xml | 4 ++-- amqp/pom.xml | 4 ++-- cli/pom.xml | 4 ++-- core/pom.xml | 4 ++-- examples/pom.xml | 4 ++-- pom.xml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index fcadcf2b..87a67c9a 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.4-SNAPSHOT + 1.4.4 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.4 tcdl diff --git a/amqp/pom.xml b/amqp/pom.xml index 3ed6a755..100d39ed 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.4-SNAPSHOT + 1.4.4 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.4 tcdl diff --git a/cli/pom.xml b/cli/pom.xml index 1d4be600..fe9ba471 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.4-SNAPSHOT + 1.4.4 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.4 tcdl diff --git a/core/pom.xml b/core/pom.xml index 05661e5d..a9b03956 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.4-SNAPSHOT + 1.4.4 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.4 tcdl diff --git a/examples/pom.xml b/examples/pom.xml index 5bbef087..68aec546 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.4-SNAPSHOT + 1.4.4 ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.4 diff --git a/pom.xml b/pom.xml index 069004bf..f0d11609 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.4-SNAPSHOT + 1.4.4 msb java msb java pom @@ -11,7 +11,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - HEAD + msb-java-1.4.4 From fa8310239c7513059ed2fdc28799f54d5f0eb525 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Thu, 10 Mar 2016 23:53:59 +0200 Subject: [PATCH 096/226] [maven-release-plugin] prepare for next development iteration --- acceptance/pom.xml | 4 ++-- amqp/pom.xml | 4 ++-- cli/pom.xml | 4 ++-- core/pom.xml | 4 ++-- examples/pom.xml | 4 ++-- pom.xml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/acceptance/pom.xml b/acceptance/pom.xml index 87a67c9a..9a302e21 100644 --- a/acceptance/pom.xml +++ b/acceptance/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.4 + 1.4.5-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.4 + HEAD tcdl diff --git a/amqp/pom.xml b/amqp/pom.xml index 100d39ed..3e0420f1 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.4 + 1.4.5-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.4 + HEAD tcdl diff --git a/cli/pom.xml b/cli/pom.xml index fe9ba471..4ff720aa 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.4 + 1.4.5-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.4 + HEAD tcdl diff --git a/core/pom.xml b/core/pom.xml index a9b03956..fabf5482 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.4 + 1.4.5-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.4 + HEAD tcdl diff --git a/examples/pom.xml b/examples/pom.xml index 68aec546..f082fb4a 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.4.4 + 1.4.5-SNAPSHOT ../pom.xml 4.0.0 @@ -15,7 +15,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.4 + HEAD diff --git a/pom.xml b/pom.xml index f0d11609..6454099b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.4.4 + 1.4.5-SNAPSHOT msb java msb java pom @@ -11,7 +11,7 @@ scm:git:https://github.com/tcdl/msb-java.git scm:git:git@github.com:tcdl/msb-java.git https://github.com/tcdl/msb-java - msb-java-1.4.4 + HEAD From c5ddb11560675f838f1e7c817f76983874cf1bb9 Mon Sep 17 00:00:00 2001 From: Nikita Galkin Date: Mon, 14 Mar 2016 11:28:00 +0200 Subject: [PATCH 097/226] Rename schema.js to schema.json --- core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java | 2 +- core/src/main/resources/{schema.js => schema.json} | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) rename core/src/main/resources/{schema.js => schema.json} (93%) diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java index 1fc6f795..bc2a7401 100644 --- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java +++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java @@ -62,7 +62,7 @@ public MsbConfig(Config loadedConfig) { private String readJsonSchema() { try { - return IOUtils.toString(getClass().getResourceAsStream("/schema.js")); + return IOUtils.toString(getClass().getResourceAsStream("/schema.json")); } catch (IOException e) { LOG.error("Failed to load Json validation schema", this); return null; diff --git a/core/src/main/resources/schema.js b/core/src/main/resources/schema.json similarity index 93% rename from core/src/main/resources/schema.js rename to core/src/main/resources/schema.json index 3324b250..959f3167 100644 --- a/core/src/main/resources/schema.js +++ b/core/src/main/resources/schema.json @@ -14,7 +14,8 @@ "type": ["object", "null"], "properties": { "to": { "$ref": "#/definitions/topic" }, - "response": { "$ref": "#/definitions/topic" } + "response": { "$ref": "#/definitions/topic" }, + "forward": { "$ref": "#/definitions/topic" } }, "required": ["to"] }, From c9f1f0fda640428e7962a5810a1b45b07b96f5ce Mon Sep 17 00:00:00 2001 From: Nikita Galkin Date: Wed, 16 Mar 2016 13:06:59 +0200 Subject: [PATCH 098/226] Update schema.json --- core/src/main/resources/schema.json | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/core/src/main/resources/schema.json b/core/src/main/resources/schema.json index 959f3167..5e18852f 100644 --- a/core/src/main/resources/schema.json +++ b/core/src/main/resources/schema.json @@ -5,15 +5,15 @@ "id": { "type": "string" }, "correlationId": { "type": "string" }, "tags": { - "type": "array", - "items": { - "type": "string" - } + "type": "array", + "items": { + "type": "string" + } }, "topics": { "type": ["object", "null"], "properties": { - "to": { "$ref": "#/definitions/topic" }, + "to": { "$ref": "#/definitions/topic" }, "response": { "$ref": "#/definitions/topic" }, "forward": { "$ref": "#/definitions/topic" } }, @@ -29,7 +29,17 @@ "serviceDetails": { "$ref": "#/definitions/serviceDetails" } }, "required": ["createdAt"] - } + }, + "ack": { + "type": ["object", "null"], + "properties": { + "responderId": { "type": "string" }, + "responsesRemaining": { "type": "number"}, + "timeoutMs": { "type": ["number", "null"] } + }, + "required": ["responderId", "responsesRemaining"] + }, + "payload": {} }, "required": ["id", "correlationId", "meta"], "definitions": { From 9e2ac2ed60d7269a5c77e63d77939f4f2b1e595f Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Fri, 18 Mar 2016 01:37:31 +0200 Subject: [PATCH 099/226] Fix for WEB-19507 --- .../tcdl/msb/config/ServiceDetails.java | 36 ++++++++----- .../tcdl/msb/config/ServiceDetailsTest.java | 52 ++++++++++++++++++- 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/config/ServiceDetails.java b/core/src/main/java/io/github/tcdl/msb/config/ServiceDetails.java index 96dca93d..1e64abb9 100644 --- a/core/src/main/java/io/github/tcdl/msb/config/ServiceDetails.java +++ b/core/src/main/java/io/github/tcdl/msb/config/ServiceDetails.java @@ -1,20 +1,20 @@ package io.github.tcdl.msb.config; -import static io.github.tcdl.msb.config.ConfigurationUtil.getString; import static io.github.tcdl.msb.config.ConfigurationUtil.getOptionalString; +import static io.github.tcdl.msb.config.ConfigurationUtil.getString; import io.github.tcdl.msb.support.Utils; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.typesafe.config.Config; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Class contains configuration data related to service instance. */ @@ -52,11 +52,13 @@ public static class Builder { public Builder(Config config) { name = getString(config, "name"); version = getString(config, "version"); - Optional optionalInstanceId = getOptionalString(config, "instanceId"); + Optional optionalInstanceId = getOptionalString(config, "instanceId"); instanceId = optionalInstanceId.isPresent() ? optionalInstanceId.get() : Utils.generateId(); - hostname = getHostInfo().getHostName(); - ip = getHostInfo().getHostAddress(); + InetAddress hostInfo = getHostInfo(); + hostname = hostInfo != null ? hostInfo.getHostName() : "unknown"; + ip = hostInfo != null ? hostInfo.getHostAddress() : null; + pid = getPID(); } @@ -67,19 +69,27 @@ public ServiceDetails build() { return new ServiceDetails(name, version, instanceId, hostname, ip, pid); } - private static InetAddress getHostInfo() { - InetAddress hostInfo = null; + protected InetAddress getHostInfo() { + InetAddress hostInfo; try { hostInfo = InetAddress.getLocalHost(); } catch (UnknownHostException ex) { - LOG.error("Fail to retrieve host info", ex); + LOG.warn("Fail to retrieve host info", ex); + hostInfo = null; } return hostInfo; } - private static long getPID() { + protected long getPID() { String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); - return Long.parseLong(processName.split("@")[0]); + long pid; + try { + pid = Long.parseLong(processName.split("@")[0]); + } catch (NumberFormatException ex) { + LOG.warn("Fail to get Process ID", ex); + pid = 0; + } + return pid; } } @@ -110,7 +120,7 @@ public long getPid() { @Override public String toString() { return "ServiceDetails [name=" + name + ", version=" + version + ", instanceId=" + instanceId + ", hostname=" - + hostname + ", ip=" + ip + ", pid=" + pid + "]"; + + hostname + String.valueOf(ip != null ? ", ip=" + ip : "") + ", pid=" + pid + "]"; } } \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/config/ServiceDetailsTest.java b/core/src/test/java/io/github/tcdl/msb/config/ServiceDetailsTest.java index cb10ebb1..45f5fc1a 100644 --- a/core/src/test/java/io/github/tcdl/msb/config/ServiceDetailsTest.java +++ b/core/src/test/java/io/github/tcdl/msb/config/ServiceDetailsTest.java @@ -3,7 +3,17 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import io.github.tcdl.msb.api.exception.ConfigurationException; +import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.api.message.MetaMessage; +import io.github.tcdl.msb.api.message.Topics; +import io.github.tcdl.msb.support.TestUtils; +import io.github.tcdl.msb.support.Utils; + +import java.net.InetAddress; +import java.time.Clock; import org.junit.Test; @@ -30,6 +40,47 @@ public void testServiceDetailsAll() { assertNotNull("expect Pid is not null", serviceDetails.getPid()); } + @Test + public void testServiceDetailsWithUnresolvedHostInfo() { + + String serviceDetailsConfigStr = String.format("serviceDetails = {name = \"%s\", version = \"%s\", instanceId = \"%s\"}", name, version, instanceId); + Config config = ConfigFactory.parseString(serviceDetailsConfigStr); + ServiceDetails serviceDetails = new ServiceDetails.Builder(config.getConfig("serviceDetails")) { + + @Override + protected InetAddress getHostInfo() { + return null; + } + + }.build(); + + //Verify object methods + assertTrue("expect Hostname is \"unknown\"", "unknown".equals(serviceDetails.getHostname())); + assertNull("expect Ip is null", serviceDetails.getIp()); + + //Verify toString() + assertTrue("expect to find \"hostname=unknown\" substring", + serviceDetails.toString().indexOf("hostname=unknown") > 0); + assertTrue("expect not to find \"ip=\" substring", + serviceDetails.toString().indexOf("ip=") < 0); + + //Verify Json serialization + Clock clock = Clock.systemDefaultZone(); + Message message = new Message.Builder() + .withMetaBuilder(new MetaMessage.Builder(null, clock.instant(), serviceDetails, clock)) + .withId("1111") + .withCorrelationId("2222") + .withTopics(new Topics("TopicTo", null, null)) + .build(); + + String jsonString = Utils.toJson(message, TestUtils.createMessageMapper()); + assertTrue("expect to find \"\"hostname\":\"unknown\"\" substring", + jsonString.indexOf("\"hostname\":\"unknown\"") > 0); + assertTrue("expect not to find \"ip\" substring", + jsonString.indexOf("ip") < 0); + + } + @Test(expected = ConfigurationException.class) public void testServiceDetailsWithoutName() { String serviceDetailsConfigStr = String.format("serviceDetails = {version = \"%s\", instanceId = \"%s\"}", version, instanceId); @@ -73,5 +124,4 @@ public void testNotRepeatableInstanceId() { assertNotEquals("expect different InstanceId values", instanceId1, instanceId2); } - } From afcaafdb5b67e7460f0c25686f99e16b44a9c387 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Fri, 18 Mar 2016 01:51:46 +0200 Subject: [PATCH 100/226] updated release-notes --- release-notes.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/release-notes.html b/release-notes.html index 15409c77..197d7140 100644 --- a/release-notes.html +++ b/release-notes.html @@ -4,18 +4,22 @@ -

Welcome to MSB-Java version 1.4.4

+

Welcome to MSB-Java version 1.4.5

March 10, 2016

 
 ------------------------------------------------------------------------------------
+Features of MSB-Java version 1.4.5:
+   - Updated messages schema validation
+   - Fixed issue with unresolved host name
+   
 Features of MSB-Java version 1.4.4:
    - MsbContext shutdown callbacks support;
-   - incoming messages schema validation is disabled by default;
+   - Incoming messages schema validation is disabled by default;
    - AMQP default heartbeat interval increased to 30s;
-   - performance improvements;
+   - Performance improvements;
    - MSB mocking support included, see MSB-TEST-MOCKING.md for details.
 
 Features of MSB-Java version 1.4.3:

From 79e1af9b607fc2e92e7afc8f92b1b7311997e93a Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 18 Mar 2016 18:39:25 +0200
Subject: [PATCH 101/226] Used Optional

---
 .../github/tcdl/msb/config/ServiceDetails.java  | 17 +++++++++--------
 .../tcdl/msb/config/ServiceDetailsTest.java     |  5 +++--
 2 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/config/ServiceDetails.java b/core/src/main/java/io/github/tcdl/msb/config/ServiceDetails.java
index 1e64abb9..e96ef954 100644
--- a/core/src/main/java/io/github/tcdl/msb/config/ServiceDetails.java
+++ b/core/src/main/java/io/github/tcdl/msb/config/ServiceDetails.java
@@ -52,12 +52,13 @@ public static class Builder {
         public Builder(Config config) {
             name = getString(config, "name");
             version = getString(config, "version");
+            
             Optional optionalInstanceId = getOptionalString(config, "instanceId");
             instanceId = optionalInstanceId.isPresent() ? optionalInstanceId.get() : Utils.generateId(); 
 
-            InetAddress hostInfo = getHostInfo();
-            hostname = hostInfo != null ? hostInfo.getHostName() : "unknown";
-            ip = hostInfo != null ? hostInfo.getHostAddress() : null;
+            Optional optionalHostInfo = getHostInfo();
+            hostname = optionalHostInfo.isPresent() ? optionalHostInfo.get().getHostName() : "unknown";
+            ip = optionalHostInfo.isPresent() ? optionalHostInfo.get().getHostAddress() : null;
             
             pid = getPID();
         }
@@ -69,15 +70,15 @@ public ServiceDetails build() {
             return new ServiceDetails(name, version, instanceId, hostname, ip, pid);
         }
 
-        protected InetAddress getHostInfo() {
-            InetAddress hostInfo;
+        protected Optional getHostInfo() {
+            Optional optionalHostInfo;
             try {
-                hostInfo = InetAddress.getLocalHost();
+                optionalHostInfo = Optional.of(InetAddress.getLocalHost());
             } catch (UnknownHostException ex) {
                 LOG.warn("Fail to retrieve host info", ex);
-                hostInfo = null;
+                optionalHostInfo = Optional.empty();
             }
-            return hostInfo;
+            return optionalHostInfo;
         }
 
         protected long getPID() {
diff --git a/core/src/test/java/io/github/tcdl/msb/config/ServiceDetailsTest.java b/core/src/test/java/io/github/tcdl/msb/config/ServiceDetailsTest.java
index 45f5fc1a..1041f7dc 100644
--- a/core/src/test/java/io/github/tcdl/msb/config/ServiceDetailsTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/config/ServiceDetailsTest.java
@@ -14,6 +14,7 @@
 
 import java.net.InetAddress;
 import java.time.Clock;
+import java.util.Optional;
 
 import org.junit.Test;
 
@@ -48,8 +49,8 @@ public void testServiceDetailsWithUnresolvedHostInfo() {
         ServiceDetails serviceDetails  = new ServiceDetails.Builder(config.getConfig("serviceDetails")) {
 
             @Override
-            protected InetAddress getHostInfo() {
-                return null;
+            protected Optional getHostInfo() {
+                return Optional.empty();
             }
             
         }.build();

From e58d3d31f83284bf7ac25ace6e12e73571987cdd Mon Sep 17 00:00:00 2001
From: rdrozdov-tc 
Date: Fri, 25 Mar 2016 13:43:55 +0200
Subject: [PATCH 102/226] custom error handler

---
 .../io/github/tcdl/msb/api/ObjectFactory.java | 21 ++++
 .../io/github/tcdl/msb/api/Requester.java     | 10 ++
 .../github/tcdl/msb/api/ResponderServer.java  | 14 +++
 .../github/tcdl/msb/collector/Collector.java  | 28 +++---
 .../github/tcdl/msb/events/EventHandlers.java | 24 ++++-
 .../tcdl/msb/impl/ObjectFactoryImpl.java      | 11 ++-
 .../github/tcdl/msb/impl/RequesterImpl.java   | 15 ++-
 .../tcdl/msb/impl/ResponderServerImpl.java    | 33 +++++--
 .../tcdl/msb/impl/RequesterImplTest.java      | 96 ++++++++++++++-----
 .../msb/impl/ResponderServerImplTest.java     | 64 +++++++++----
 .../mock/objectfactory/ResponderCapture.java  | 10 +-
 .../objectfactory/TestMsbObjectFactory.java   | 29 +++++-
 12 files changed, 279 insertions(+), 76 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
index 8a4acb79..a32edbc3 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
@@ -68,6 +68,16 @@ public Type getType() {
         });
     }
 
+    default  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
+            ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) {
+        return createResponderServer(namespace, messageTemplate, requestHandler, errorHandler, new TypeReference() {
+            @Override
+            public Type getType() {
+                return payloadClass;
+            }
+        });
+    }
+
     /**
      * @param namespace                 topic on a bus for listening on incoming requests
      * @param messageTemplate           template used for creating response messages
@@ -78,6 +88,17 @@ public Type getType() {
      ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference);
 
+    /**
+     * @param namespace                 topic on a bus for listening on incoming requests
+     * @param messageTemplate           template used for creating response messages
+     * @param requestHandler            handler for processing the request
+     * @param errorHandler              handler for errors to be called after default
+     * @param payloadTypeReference      expected payload type of incoming messages
+     * @return new instance of a {@link ResponderServer} that unmarshals payload into specified payload type
+     */
+     ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
+            ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference);
+
     /**
      * @return instance of converter to convert any objects
      * using object mapper from {@link MsbContext}
diff --git a/core/src/main/java/io/github/tcdl/msb/api/Requester.java b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
index 32ee940c..0b56f7e0 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/Requester.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
@@ -95,4 +95,14 @@ public interface Requester {
      * @return requester
      */
     Requester onEnd(Callback endHandler);
+
+    /**
+     * Registers a callback to be called if an error is encountered during receiving a response from the bus
+     * Will be invoked only after all incoming responses will be processed.
+     *
+     * @param errorHandler callback to be called
+     * @return requester
+     */
+    Requester onError(BiConsumer errorHandler);
+
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java b/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
index cce3fb6a..96fc4318 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
@@ -1,5 +1,7 @@
 package io.github.tcdl.msb.api;
 
+import io.github.tcdl.msb.api.message.Message;
+
 /**
  * {@link ResponderServer} enable user to listen on messages from the bus and executing microservice business logic.
  * Call to {@link #listen()} method will start listening on incoming messages from the bus.
@@ -32,4 +34,16 @@ interface RequestHandler {
         void process(T request, ResponderContext responderContext) throws Exception;
     }
 
+    /**
+     * Implementation of this interface contains custom error handler
+     */
+    interface ErrorHandler {
+        /**
+         * Executes user defined error handler
+         *
+         * @param exception error cause
+         * @param originalMessage original message
+         */
+        void handle(Exception exception, Message originalMessage);
+    }
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
index 9c8060fc..df998e91 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
@@ -1,8 +1,8 @@
 package io.github.tcdl.msb.collector;
 
-import static io.github.tcdl.msb.support.Utils.ifNull;
-import static java.lang.Math.toIntExact;
-
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import io.github.tcdl.msb.api.AcknowledgementHandler;
 import io.github.tcdl.msb.api.Callback;
 import io.github.tcdl.msb.api.MessageContext;
@@ -13,21 +13,25 @@
 import io.github.tcdl.msb.impl.MessageContextImpl;
 import io.github.tcdl.msb.impl.MsbContextImpl;
 import io.github.tcdl.msb.support.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.time.Clock;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
-import java.util.*;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.atomic.LongAdder;
 import java.util.function.BiConsumer;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import static io.github.tcdl.msb.support.Utils.ifNull;
+import static java.lang.Math.toIntExact;
 
 /**
  * {@link Collector} is a component which collects responses and acknowledgements for sent requests.
@@ -64,6 +68,7 @@ public class Collector implements ConsumedMessagesAwareMessageHandler {
     private final Optional> onResponse;
     private final Optional> onAcknowledge;
     private final Optional> onEnd;
+    private final Optional> onError;
 
     private ScheduledFuture ackTimeoutFuture;
     private ScheduledFuture responseTimeoutFuture;
@@ -130,7 +135,7 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt
         onResponse = Optional.ofNullable(eventHandlers.onResponse());
         onAcknowledge = Optional.ofNullable(eventHandlers.onAcknowledge());
         onEnd = Optional.ofNullable(eventHandlers.onEnd());
-
+        onError = Optional.ofNullable(eventHandlers.onError());
     }
 
     @Override
@@ -182,6 +187,7 @@ public void handleMessage(Message incomingMessage, AcknowledgementHandler acknow
             } catch (Exception e) {
                 //do not propagate exception outside of this method in order to prevent autoRetry for responses
                 LOG.warn("Unexpected exception during response handler invocation", e);
+                onError.ifPresent(handler -> handler.accept(e, incomingMessage));
             }
         } else {
             LOG.debug("[correlation ids: {}-{}] Received {}",
diff --git a/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java b/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java
index bd0056d6..3d249047 100644
--- a/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java
+++ b/core/src/main/java/io/github/tcdl/msb/events/EventHandlers.java
@@ -17,7 +17,8 @@ public class EventHandlers {
     private BiConsumer onResponse = (acknowledge, msgContext) -> {};
     private BiConsumer onRawResponse = (acknowledge, msgContext) -> {};
     private Callback onEnd = messages -> {};
-    
+    private BiConsumer onError;
+
     /**
      * Return callback registered for Acknowledge event.
      *
@@ -99,4 +100,25 @@ public EventHandlers onEnd(Callback onEnd) {
         this.onEnd = onEnd;
         return this;
     }
+
+    /**
+     * Registered callback for Error event.
+     *
+     * @param onError callback
+     * @return EventHandlers
+     */
+    public EventHandlers onError(BiConsumer  onError) {
+        this.onError = onError;
+        return this;
+    }
+
+    /**
+     * Return callback registered for Error event.
+     *
+     * @return error callback
+     */
+    public BiConsumer onError() {
+        return onError;
+    }
+
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
index d11d1f7c..ade2afe1 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
@@ -46,7 +46,16 @@ public  Requester createRequester(String namespace, RequestOptions request
     @Override
     public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) {
-        return ResponderServerImpl.create(namespace, messageTemplate, msbContext, requestHandler, payloadTypeReference);
+        return ResponderServerImpl.create(namespace, messageTemplate, msbContext, requestHandler, null, payloadTypeReference);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
+            ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) {
+        return ResponderServerImpl.create(namespace, messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference);
     }
 
     /**
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
index cc928be1..be1e7fb7 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
@@ -1,5 +1,6 @@
 package io.github.tcdl.msb.impl;
 
+import com.fasterxml.jackson.core.type.TypeReference;
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.api.Callback;
 import io.github.tcdl.msb.api.MessageContext;
@@ -11,12 +12,9 @@
 import io.github.tcdl.msb.collector.Collector;
 import io.github.tcdl.msb.events.EventHandlers;
 import io.github.tcdl.msb.message.MessageFactory;
-
-import java.util.function.BiConsumer;
-
 import org.apache.commons.lang3.Validate;
 
-import com.fasterxml.jackson.core.type.TypeReference;
+import java.util.function.BiConsumer;
 
 /**
  * Implementation of {@link Requester}
@@ -166,6 +164,15 @@ public Requester onEnd(Callback endHandler) {
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Requester onError(BiConsumer errorHandler) {
+        eventHandlers.onError(errorHandler);
+        return this;
+    }
+
     private ChannelManager getChannelManager() {
         return context.getChannelManager();
     }
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
index 656152ea..af8044f8 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
@@ -1,5 +1,7 @@
 package io.github.tcdl.msb.impl;
 
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.api.AcknowledgementHandler;
 import io.github.tcdl.msb.api.MessageTemplate;
@@ -10,13 +12,11 @@
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.support.Utils;
-
 import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.Optional;
 
 public class ResponderServerImpl implements ResponderServer {
     private static final Logger LOG = LoggerFactory.getLogger(ResponderServerImpl.class);
@@ -25,25 +25,32 @@ public class ResponderServerImpl implements ResponderServer {
     private MsbContextImpl msbContext;
     private MessageTemplate messageTemplate;
     private RequestHandler requestHandler;
+    private Optional errorHandler;
     private ObjectMapper payloadMapper;
     private TypeReference payloadTypeReference;
 
-    private ResponderServerImpl(String namespace, MessageTemplate messageTemplate, MsbContextImpl msbContext, RequestHandler requestHandler, TypeReference payloadTypeReference) {
+    private ResponderServerImpl(String namespace,
+            MessageTemplate messageTemplate,
+            MsbContextImpl msbContext,
+            RequestHandler requestHandler,
+            ErrorHandler errorHandler,
+            TypeReference payloadTypeReference) {
         this.namespace = namespace;
         this.messageTemplate = messageTemplate;
         this.msbContext = msbContext;
         this.requestHandler = requestHandler;
+        this.errorHandler = Optional.ofNullable(errorHandler);
         this.payloadMapper = msbContext.getPayloadMapper();
         this.payloadTypeReference = payloadTypeReference;
         Validate.notNull(requestHandler, "requestHandler must not be null");
     }
 
     /**
-     * {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(String, MessageTemplate, RequestHandler, Class)}
+     * {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(String, MessageTemplate, RequestHandler, ErrorHandler, Class)}
      */
     static  ResponderServerImpl create(String namespace,  MessageTemplate messageTemplate, MsbContextImpl msbContext,
-            RequestHandler requestHandler, TypeReference payloadTypeReference) {
-        return new ResponderServerImpl<>(namespace, messageTemplate, msbContext, requestHandler, payloadTypeReference);
+            RequestHandler requestHandler,  ErrorHandler errorHandler, TypeReference payloadTypeReference) {
+        return new ResponderServerImpl<>(namespace, messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference);
     }
 
     /**
@@ -89,9 +96,17 @@ void onResponder(ResponderContext responderContext) {
             LOG.debug("[{}] Process message with id: [{}]", namespace, originalMessage.getId());
             requestHandler.process(request, responderContext);
         } catch (JsonConversionException conversionEx) {
-            errorHandler(responderContext, conversionEx, PAYLOAD_CONVERSION_ERROR_CODE);
+            if (errorHandler.isPresent()) {
+                errorHandler.get().handle(conversionEx, originalMessage);
+            } else {
+                errorHandler(responderContext, conversionEx, PAYLOAD_CONVERSION_ERROR_CODE);
+            }
         } catch (Exception internalEx) {
-            errorHandler(responderContext, internalEx, INTERNAL_SERVER_ERROR_CODE);
+            if (errorHandler.isPresent()) {
+                errorHandler.get().handle(internalEx, originalMessage);
+            } else {
+                errorHandler(responderContext, internalEx, INTERNAL_SERVER_ERROR_CODE);
+            }
         }
     }
 
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
index f324c356..b29f40ba 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
@@ -1,16 +1,11 @@
 package io.github.tcdl.msb.impl;
 
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.*;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.*;
-
+import com.fasterxml.jackson.core.type.TypeReference;
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.Consumer;
 import io.github.tcdl.msb.Producer;
 import io.github.tcdl.msb.api.Callback;
+import io.github.tcdl.msb.api.MessageContext;
 import io.github.tcdl.msb.api.MessageTemplate;
 import io.github.tcdl.msb.api.RequestOptions;
 import io.github.tcdl.msb.api.Requester;
@@ -18,17 +13,32 @@
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.collector.Collector;
 import io.github.tcdl.msb.support.TestUtils;
-
-import java.time.Clock;
-import java.util.function.BiConsumer;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import com.fasterxml.jackson.core.type.TypeReference;
+import java.time.Clock;
+import java.util.function.BiConsumer;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertArrayEquals;
+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.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 /**
  * Created by rdro on 4/27/2015.
@@ -52,7 +62,7 @@ public class RequesterImplTest {
 
     @Test
     public void testPublishNoWaitForResponses() throws Exception {
-        RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null);
+        RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null, null, null);
 
         publishByAllMethods(requester);
 
@@ -62,7 +72,7 @@ public void testPublishNoWaitForResponses() throws Exception {
 
     @Test
     public void testPublishWaitForResponses() throws Exception {
-        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null);
+        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null);
 
         publishByAllMethods(requester);
 
@@ -80,18 +90,31 @@ private void publishByAllMethods(RequesterImpl requester) {
 
     @Test
     public void testPublishWaitForResponsesAck() throws Exception {
-        RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, arg ->  fail());
+        RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, null, null, arg ->  fail());
+
+        requester.publish(TestUtils.createSimpleRequestPayload());
 
+        Message responseMessage = TestUtils.createMsbRequestMessage("some:topic", "body text");
+        collectorMock.handleMessage(responseMessage, null);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPublishHandleErrorResponse() throws Exception {
+        RuntimeException ex = new RuntimeException();
+        BiConsumer errorHandlerMock = mock(BiConsumer.class);
+        RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, (p, c) -> { throw ex; }, errorHandlerMock, arg ->  fail());
         requester.publish(TestUtils.createSimpleRequestPayload());
 
         Message responseMessage = TestUtils.createMsbRequestMessage("some:topic", "body text");
         collectorMock.handleMessage(responseMessage, null);
+        verify(errorHandlerMock).accept(eq(ex), eq(responseMessage));
     }
 
     @Test
     public void testProducerPublishWithPayload() throws Exception {
         String bodyText = "Body text";
-        RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null);
+        RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null, null, null);
         ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class);
         RestPayload payload = TestUtils.createPayloadWithTextBody(bodyText);
 
@@ -105,7 +128,7 @@ public void testProducerPublishWithPayload() throws Exception {
     @SuppressWarnings("unchecked")
     public void testAcknowledgeEventHandlerIsAdded() throws Exception {
         BiConsumer onAckMock = mock(BiConsumer.class);
-        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null);
+        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null);
 
         requester.onAcknowledge(onAckMock);
 
@@ -113,13 +136,14 @@ public void testAcknowledgeEventHandlerIsAdded() throws Exception {
         assertThat(requester.eventHandlers.onResponse(), not(onAckMock));
         assertThat(requester.eventHandlers.onRawResponse(), not(onAckMock));
         assertThat(requester.eventHandlers.onEnd(), not(onAckMock));
+        assertThat(requester.eventHandlers.onError(), not(onAckMock));
     }
 
     @Test
     @SuppressWarnings("unchecked")
     public void testResponseEventHandlerIsAdded() throws Exception {
         BiConsumer onResponseMock = mock(BiConsumer.class);
-        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null);
+        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null);
 
         requester.onResponse(onResponseMock);
 
@@ -127,14 +151,14 @@ public void testResponseEventHandlerIsAdded() throws Exception {
         assertThat(requester.eventHandlers.onResponse(), is(onResponseMock));
         assertThat(requester.eventHandlers.onRawResponse(), not(onResponseMock));
         assertThat(requester.eventHandlers.onEnd(), not(onResponseMock));
+        assertThat(requester.eventHandlers.onError(), not(onResponseMock));
     }
 
-
     @Test
     @SuppressWarnings("unchecked")
     public void testRawResponseEventHandlerIsAdded() throws Exception {
         BiConsumer onRawResponseMock = mock(BiConsumer.class);
-        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null);
+        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null);
 
         requester.onRawResponse(onRawResponseMock);
 
@@ -142,14 +166,14 @@ public void testRawResponseEventHandlerIsAdded() throws Exception {
         assertThat(requester.eventHandlers.onRawResponse(), is(onRawResponseMock));
         assertThat(requester.eventHandlers.onResponse(), not(onRawResponseMock));
         assertThat(requester.eventHandlers.onEnd(), not(onRawResponseMock));
+        assertThat(requester.eventHandlers.onError(), not(onRawResponseMock));
     }
 
-
     @Test
     @SuppressWarnings("unchecked")
     public void testEndEventHandlerIsAdded() throws Exception {
         Callback onEndMock = mock(Callback.class);
-        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null);
+        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0,null, null, null);
 
         requester.onEnd(onEndMock);
 
@@ -157,18 +181,35 @@ public void testEndEventHandlerIsAdded() throws Exception {
         assertThat(requester.eventHandlers.onResponse(), not(onEndMock));
         assertThat(requester.eventHandlers.onRawResponse(), not(onEndMock));
         assertThat(requester.eventHandlers.onEnd(), is(onEndMock));
+        assertThat(requester.eventHandlers.onError(), not(onEndMock));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testErrorEventHandlerIsAdded() throws Exception {
+        BiConsumer onErrorMock = mock(BiConsumer.class);
+        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0,null, null, null);
+
+        requester.onError(onErrorMock);
+
+        assertThat(requester.eventHandlers.onAcknowledge(), not(onErrorMock));
+        assertThat(requester.eventHandlers.onResponse(), not(onErrorMock));
+        assertThat(requester.eventHandlers.onRawResponse(), not(onErrorMock));
+        assertThat(requester.eventHandlers.onEnd(), not(onErrorMock));
+        assertThat(requester.eventHandlers.onError(), is(onErrorMock));
     }
 
     @Test
     @SuppressWarnings("unchecked")
     public void testNoEventHandlerAdded() throws Exception {
         Callback onEndMock = mock(Callback.class);
-        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null);
+        RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null);
 
         assertThat(requester.eventHandlers.onAcknowledge(), not(onEndMock));
         assertThat(requester.eventHandlers.onResponse(), not(onEndMock));
         assertThat(requester.eventHandlers.onRawResponse(), not(onEndMock));
         assertThat(requester.eventHandlers.onEnd(), not(onEndMock));
+        assertThat(requester.eventHandlers.onError(), not(onEndMock));
     }
 
     @Test
@@ -247,7 +288,10 @@ public void testRequestMessageWithForward() throws Exception {
         assertEquals(forwardNamespace, requestMessage.getTopics().getForward());
     }
 
-    private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout,  Integer ackTimeout , Callback endHandler) throws Exception {
+    private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout,  Integer ackTimeout,
+            BiConsumer onResponse,
+            BiConsumer onError,
+            Callback endHandler) throws Exception {
 
         MessageTemplate messageTemplateMock = mock(MessageTemplate.class);
 
@@ -265,7 +309,9 @@ private RequesterImpl initRequesterForResponsesWith(Integer numberO
                 .build();
 
         RequesterImpl requester = spy(RequesterImpl.create(NAMESPACE, requestOptionsMock, msbContext, new TypeReference() {}));
-        requester.onEnd(endHandler);
+        requester.onResponse(onResponse)
+                 .onError(onError)
+                 .onEnd(endHandler);
 
         collectorMock = spy(new Collector<>(NAMESPACE, TestUtils.createMsbRequestMessageNoPayload(NAMESPACE), requestOptionsMock, msbContext, requester.eventHandlers,
                 new TypeReference() {}));
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
index 9bb189b8..5b1e55f9 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
@@ -1,16 +1,6 @@
 package io.github.tcdl.msb.impl;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
+import com.fasterxml.jackson.core.type.TypeReference;
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.Producer;
@@ -23,14 +13,24 @@
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.support.TestUtils;
-
-import java.util.Map;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
-import com.fasterxml.jackson.core.type.TypeReference;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 public class ResponderServerImplTest {
 
@@ -61,7 +61,7 @@ public void testResponderServerProcessPayloadSuccess() throws Exception {
         when(spyMsbContext.getChannelManager()).thenReturn(spyChannelManager);
 
         ResponderServerImpl, Object, Map>> responderServer = ResponderServerImpl
-                .create(TOPIC, requestOptions.getMessageTemplate(), spyMsbContext, handler, 
+                .create(TOPIC, requestOptions.getMessageTemplate(), spyMsbContext, handler, null,
                         new TypeReference, Object, Map>>() {});
 
         ResponderServerImpl spyResponderServer = (ResponderServerImpl) spy(responderServer).listen();
@@ -90,7 +90,7 @@ public void testResponderServerProcessUnexpectedPayload() throws Exception {
         Message incomingMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
 
         ResponderServerImpl responderServer = ResponderServerImpl
-                .create(TOPIC, messageTemplate, msbContext, handler, new TypeReference() {});
+                .create(TOPIC, messageTemplate, msbContext, handler, null, new TypeReference() {});
         responderServer.listen();
 
         // simulate incoming request
@@ -116,7 +116,7 @@ public void testResponderServerProcessHandlerThrowException() throws Exception {
         };
 
         ResponderServerImpl responderServer = ResponderServerImpl
-                .create(TOPIC, messageTemplate, msbContext, handler, new TypeReference() {});
+                .create(TOPIC, messageTemplate, msbContext, handler, null, new TypeReference() {});
         responderServer.listen();
 
         // simulate incoming request
@@ -136,6 +136,30 @@ public void testResponderServerProcessHandlerThrowException() throws Exception {
         assertEquals(exceptionMessage, responseCaptor.getValue().getStatusMessage());
     }
 
+    @Test
+    public void testResponderServerProcessCustomHandlerThrowException() throws Exception {
+        String exceptionMessage = "Test exception message";
+        Exception error = new Exception(exceptionMessage);
+        ResponderServer.RequestHandler handler = (request, responderContext) -> {
+            throw error;
+        };
+
+        ResponderServer.ErrorHandler errorHandlerMock = mock(ResponderServer.ErrorHandler.class);
+        ResponderServerImpl responderServer = ResponderServerImpl
+                .create(TOPIC, messageTemplate, msbContext, handler, errorHandlerMock, new TypeReference() {});
+        responderServer.listen();
+
+        // simulate incoming request
+        Message originalMessage = TestUtils.createMsbRequestMessageNoPayload(TOPIC);
+        Responder responder = mock(Responder.class);
+        AcknowledgementHandler acknowledgeHandler = mock(AcknowledgementHandler.class);
+        ResponderContext responderContext = responderServer.createResponderContext(responder, acknowledgeHandler, originalMessage);
+
+        responderServer.onResponder(responderContext);
+
+        verify(errorHandlerMock).handle(eq(error), eq(originalMessage));
+    }
+
     @Test
     public void testCreateResponderWithResponseTopic() {
         ResponderServer.RequestHandler handler = (request, responderContext) -> {
@@ -149,7 +173,7 @@ public void testCreateResponderWithResponseTopic() {
                 .build();
 
         ResponderServerImpl responderServer = ResponderServerImpl
-                .create(TOPIC, messageTemplate, msbContext1, handler, new TypeReference() {});
+                .create(TOPIC, messageTemplate, msbContext1, handler, null, new TypeReference() {});
 
         Message incomingMessage = TestUtils.createMsbRequestMessageNoPayload(TOPIC);
         Responder responder = responderServer.createResponder(incomingMessage);
@@ -174,7 +198,7 @@ public void testCreateResponderNoResponseTopic() {
                 .build();
 
         ResponderServerImpl responderServer = ResponderServerImpl
-                .create(TOPIC, messageTemplate, msbContext, handler, new TypeReference() {});
+                .create(TOPIC, messageTemplate, msbContext, handler, null, new TypeReference() {});
 
         Message incomingMessage = TestUtils.createMsbBroadcastMessageNoPayload(TOPIC);
         Responder responder = responderServer.createResponder(incomingMessage);
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java
index bb17e30d..7d0b2685 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java
@@ -13,13 +13,17 @@
 public class ResponderCapture extends AbstractCapture  {
     private final MessageTemplate messageTemplate;
     private final ResponderServer.RequestHandler requestHandler;
+    private final ResponderServer.ErrorHandler errorHandler;
     private final ResponderServer responderServerMock;
 
-    public ResponderCapture(String namespace, MessageTemplate messageTemplate, ResponderServer.RequestHandler requestHandler,
+    public ResponderCapture(String namespace, MessageTemplate messageTemplate,
+            ResponderServer.RequestHandler requestHandler,
+            ResponderServer.ErrorHandler errorHandler,
             TypeReference payloadTypeReference, Class payloadClass) {
         super(namespace, payloadTypeReference, payloadClass);
         this.messageTemplate = messageTemplate;
         this.requestHandler = requestHandler;
+        this.errorHandler = errorHandler;
         this.responderServerMock = mock(ResponderServer.class);
     }
 
@@ -31,6 +35,10 @@ public ResponderServer.RequestHandler getRequestHandler() {
         return requestHandler;
     }
 
+    public ResponderServer.ErrorHandler getErrorHandler() {
+        return errorHandler;
+    }
+
     public ResponderServer getResponderServerMock() {
         return responderServerMock;
     }
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
index 2c6de266..7b513994 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
@@ -2,7 +2,13 @@
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.JsonNode;
-import io.github.tcdl.msb.api.*;
+import io.github.tcdl.msb.api.Callback;
+import io.github.tcdl.msb.api.MessageTemplate;
+import io.github.tcdl.msb.api.ObjectFactory;
+import io.github.tcdl.msb.api.PayloadConverter;
+import io.github.tcdl.msb.api.RequestOptions;
+import io.github.tcdl.msb.api.Requester;
+import io.github.tcdl.msb.api.ResponderServer;
 import io.github.tcdl.msb.api.monitor.AggregatorStats;
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
 
@@ -42,7 +48,15 @@ public  Requester createRequester(String namespace, RequestOptions request
     @Override
     public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) {
-        ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler,  payloadTypeReference, null);
+        ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, payloadTypeReference, null);
+        storage.addCapture(capture);
+        return capture.getResponderServerMock();
+    }
+
+    @Override
+    public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
+            ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) {
+        ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, errorHandler, payloadTypeReference, null);
         storage.addCapture(capture);
         return capture.getResponderServerMock();
     }
@@ -50,7 +64,7 @@ public  ResponderServer createResponderServer(String namespace, MessageTempla
     @Override
     public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler) {
-        ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, null);
+        ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, null, null);
         storage.addCapture(capture);
         return capture.getResponderServerMock();
     }
@@ -58,7 +72,14 @@ public ResponderServer createResponderServer(String namespace, MessageTemplate m
     @Override
     public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler, Class payloadClass) {
-        ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, payloadClass);
+        ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, null, payloadClass);
+        storage.addCapture(capture);
+        return capture.getResponderServerMock();
+    }
+
+    @Override public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
+            ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) {
+        ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, errorHandler, null, payloadClass);
         storage.addCapture(capture);
         return capture.getResponderServerMock();
     }

From 31e4545cfdf3041a341848c8dbf6b5c4a1931227 Mon Sep 17 00:00:00 2001
From: rdrozdov-tc 
Date: Wed, 30 Mar 2016 10:43:05 +0300
Subject: [PATCH 103/226] rest response on error replaced with zero ack

---
 .../tcdl/msb/impl/ResponderServerImpl.java    | 22 ++++-----------
 .../msb/impl/ResponderServerImplTest.java     | 27 ++++++-------------
 2 files changed, 13 insertions(+), 36 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
index af8044f8..96a1cc18 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
@@ -8,9 +8,7 @@
 import io.github.tcdl.msb.api.Responder;
 import io.github.tcdl.msb.api.ResponderContext;
 import io.github.tcdl.msb.api.ResponderServer;
-import io.github.tcdl.msb.api.exception.JsonConversionException;
 import io.github.tcdl.msb.api.message.Message;
-import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.support.Utils;
 import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
@@ -95,17 +93,11 @@ void onResponder(ResponderContext responderContext) {
             T request = Utils.convert(rawPayload, payloadTypeReference, payloadMapper);
             LOG.debug("[{}] Process message with id: [{}]", namespace, originalMessage.getId());
             requestHandler.process(request, responderContext);
-        } catch (JsonConversionException conversionEx) {
+        } catch (Exception e) {
             if (errorHandler.isPresent()) {
-                errorHandler.get().handle(conversionEx, originalMessage);
+                errorHandler.get().handle(e, originalMessage);
             } else {
-                errorHandler(responderContext, conversionEx, PAYLOAD_CONVERSION_ERROR_CODE);
-            }
-        } catch (Exception internalEx) {
-            if (errorHandler.isPresent()) {
-                errorHandler.get().handle(internalEx, originalMessage);
-            } else {
-                errorHandler(responderContext, internalEx, INTERNAL_SERVER_ERROR_CODE);
+                errorHandler(responderContext, e);
             }
         }
     }
@@ -114,14 +106,10 @@ private boolean isResponseNeeded(Message incomingMessage) {
         return incomingMessage.getTopics().getResponse() != null;
     }
 
-    private void errorHandler(ResponderContext responderContext, Exception exception, int errorStatusCode) {
+    private void errorHandler(ResponderContext responderContext, Exception exception) {
         Message originalMessage = responderContext.getOriginalMessage();
         LOG.error("[{}] Error while processing message with id: [{}]", namespace, originalMessage.getId(), exception);
-        RestPayload responsePayload = new RestPayload.Builder()
-                .withStatusCode(errorStatusCode)
-                .withStatusMessage(exception.getMessage())
-                .build();
-        responderContext.getResponder().send(responsePayload);
+        responderContext.getResponder().sendAck(0, 0);
         //Confirm message for prevention requeue message with incorrect structure
         responderContext.getAcknowledgementHandler().confirmMessage();
     }
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
index 5b1e55f9..344bc07b 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
@@ -20,7 +20,6 @@
 import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.anyString;
@@ -83,8 +82,7 @@ public void testResponderServerProcessErrorNoHandler() throws Exception {
 
     @Test
     public void testResponderServerProcessUnexpectedPayload() throws Exception {
-        ResponderServer.RequestHandler handler = (request, responderContext) -> {
-        };
+        ResponderServer.RequestHandler handler = (request, responderContext) -> {};
 
         String bodyText = "some body";
         Message incomingMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
@@ -94,26 +92,21 @@ public void testResponderServerProcessUnexpectedPayload() throws Exception {
         responderServer.listen();
 
         // simulate incoming request
-        ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(RestPayload.class);
-        ResponderImpl responder = spy(
-                new ResponderImpl(messageTemplate, incomingMessage, msbContext));
-        
+        ResponderImpl responder = spy(new ResponderImpl(messageTemplate, incomingMessage, msbContext));
+
         AcknowledgementHandler acknowledgeHandler = mock(AcknowledgementHandler.class);
         ResponderContext responderContext = responderServer.createResponderContext(responder, acknowledgeHandler, incomingMessage);
         
         responderServer.onResponder(responderContext);
-        verify(responder).send(responseCaptor.capture());
-        assertEquals(ResponderServer.PAYLOAD_CONVERSION_ERROR_CODE, responseCaptor.getValue().getStatusCode().intValue());
-        assertNotNull(responseCaptor.getValue().getStatusMessage());
+        verify(responder).sendAck(0, 0);
+        verify(acknowledgeHandler).confirmMessage();
     }
 
     @Test
     public void testResponderServerProcessHandlerThrowException() throws Exception {
         String exceptionMessage = "Test exception message";
         Exception error = new Exception(exceptionMessage);
-        ResponderServer.RequestHandler handler = (request, responderContext) -> {
-            throw error;
-        };
+        ResponderServer.RequestHandler handler = (request, responderContext) -> { throw error; };
 
         ResponderServerImpl responderServer = ResponderServerImpl
                 .create(TOPIC, messageTemplate, msbContext, handler, null, new TypeReference() {});
@@ -126,14 +119,10 @@ public void testResponderServerProcessHandlerThrowException() throws Exception {
                 new ResponderImpl(messageTemplate, originalMessage, msbContext));
         AcknowledgementHandler acknowledgeHandler = mock(AcknowledgementHandler.class);
         ResponderContext responderContext = responderServer.createResponderContext(responder, acknowledgeHandler, originalMessage);
-        
-        responderServer.onResponder(responderContext);
 
-        verify(responder).send(responseCaptor.capture());
+        responderServer.onResponder(responderContext);
+        verify(responder).sendAck(0, 0);
         verify(acknowledgeHandler).confirmMessage();
-        
-        assertEquals(ResponderServer.INTERNAL_SERVER_ERROR_CODE, responseCaptor.getValue().getStatusCode().intValue());
-        assertEquals(exceptionMessage, responseCaptor.getValue().getStatusMessage());
     }
 
     @Test

From 5da4362f846a2277957e77514d4e4cdcb308ae83 Mon Sep 17 00:00:00 2001
From: rdrozdov-tc 
Date: Thu, 31 Mar 2016 10:52:20 +0300
Subject: [PATCH 104/226] removed error logging during json transformations

---
 .../java/io/github/tcdl/msb/support/Utils.java     | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/support/Utils.java b/core/src/main/java/io/github/tcdl/msb/support/Utils.java
index 8ec2d622..f3ebf0a0 100644
--- a/core/src/main/java/io/github/tcdl/msb/support/Utils.java
+++ b/core/src/main/java/io/github/tcdl/msb/support/Utils.java
@@ -1,12 +1,6 @@
 
 package io.github.tcdl.msb.support;
 
-import java.io.IOException;
-import java.util.UUID;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Pattern;
-
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -17,7 +11,12 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
 import java.lang.reflect.Type;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
 
 /**
  * Created by rdro on 4/22/2015.
@@ -55,7 +54,6 @@ public static String toJson(Object object, ObjectMapper objectMapper) {
         try {
             return objectMapper.writeValueAsString(object);
         } catch (JsonProcessingException e) {
-            LOG.error("Failed to parse to JSON object: [{}] ", object);
             throw new JsonConversionException("Failed parse to JSON", e);
         }
     }
@@ -80,7 +78,6 @@ public static  T fromJson(String json, TypeReference typeReference, Object
         try {
             return objectMapper.readValue(json, typeReference);
         } catch (IOException e) {
-            LOG.error("Failed to parse from JSON: [{}] to object of type: [{}]", json, typeReference);
             throw new JsonConversionException("Failed parse from JSON", e);
         }
     }
@@ -100,7 +97,6 @@ public static  T convert(Object srcObject, TypeReference typeReference, Ob
         try {
             return objectMapper.convertValue(srcObject, typeReference);
         } catch (Exception e) {
-            LOG.error("Failed to convert object [{}] to type: [{}]", srcObject, typeReference.getType());
             throw new JsonConversionException(e.getMessage(), e);
         }
     }

From 9e56251a48f5070bdc5fe65bb5649296a9e6fee3 Mon Sep 17 00:00:00 2001
From: alex 
Date: Wed, 6 Apr 2016 15:37:01 +0300
Subject: [PATCH 105/226] Added FlowContext

---
 .../io/github/tcdl/msb/api/FlowContext.java   | 25 +++++++++++++++++++
 release-notes.html                            |  5 ++--
 2 files changed, 28 insertions(+), 2 deletions(-)
 create mode 100644 core/src/main/java/io/github/tcdl/msb/api/FlowContext.java

diff --git a/core/src/main/java/io/github/tcdl/msb/api/FlowContext.java b/core/src/main/java/io/github/tcdl/msb/api/FlowContext.java
new file mode 100644
index 00000000..9a58c58a
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/api/FlowContext.java
@@ -0,0 +1,25 @@
+package io.github.tcdl.msb.api;
+
+
+import io.github.tcdl.msb.api.message.Message;
+
+/**
+ * Gives access to initial message without polluting client classes APIs
+ * FlowContext is wrapper around {@link ThreadLocal}. Additional care has to be taken if any kind of multithreaded message processing takes place.
+ */
+public class FlowContext {
+
+    private static final ThreadLocal initialMessage = new ThreadLocal<>();
+
+    public static void setInitialMessage(Message message){
+        initialMessage.set(message);
+    }
+
+    public static Message getInitialMessage(){
+        return initialMessage.get();
+    }
+
+    public static void clear(){
+        initialMessage.remove();
+    }
+}
diff --git a/release-notes.html b/release-notes.html
index 197d7140..9365907b 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -12,8 +12,9 @@ 

March 10, 2016

------------------------------------------------------------------------------------ Features of MSB-Java version 1.4.5: - - Updated messages schema validation - - Fixed issue with unresolved host name + - Updated messages schema validation; + - Fixed issue with unresolved host name; + - Added FlowContext to support flows that involve multiple conversations between microservices. Features of MSB-Java version 1.4.4: - MsbContext shutdown callbacks support; From 679930c449114728ee5ed936aaa35b867e1095f7 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 6 Apr 2016 17:08:12 +0300 Subject: [PATCH 106/226] FlowContext renamed to MsbThreadContext. Corresponding test updated --- .../io/github/tcdl/msb/api/FlowContext.java | 25 ------------------- .../tcdl/msb/impl/MsbThreadContext.java | 25 +++++++++++++++++++ .../tcdl/msb/impl/ResponderServerImpl.java | 9 +++---- .../msb/impl/ResponderServerImplTest.java | 14 ++++++----- release-notes.html | 2 +- 5 files changed, 38 insertions(+), 37 deletions(-) delete mode 100644 core/src/main/java/io/github/tcdl/msb/api/FlowContext.java create mode 100644 core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java diff --git a/core/src/main/java/io/github/tcdl/msb/api/FlowContext.java b/core/src/main/java/io/github/tcdl/msb/api/FlowContext.java deleted file mode 100644 index 9a58c58a..00000000 --- a/core/src/main/java/io/github/tcdl/msb/api/FlowContext.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.tcdl.msb.api; - - -import io.github.tcdl.msb.api.message.Message; - -/** - * Gives access to initial message without polluting client classes APIs - * FlowContext is wrapper around {@link ThreadLocal}. Additional care has to be taken if any kind of multithreaded message processing takes place. - */ -public class FlowContext { - - private static final ThreadLocal initialMessage = new ThreadLocal<>(); - - public static void setInitialMessage(Message message){ - initialMessage.set(message); - } - - public static Message getInitialMessage(){ - return initialMessage.get(); - } - - public static void clear(){ - initialMessage.remove(); - } -} diff --git a/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java b/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java new file mode 100644 index 00000000..c1d5546b --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java @@ -0,0 +1,25 @@ +package io.github.tcdl.msb.impl; + + +import io.github.tcdl.msb.api.MessageContext; + +/** + * Gives access to initial message without polluting client classes APIs MsbThreadContext is wrapper around {@link ThreadLocal}. Additional care has to be taken + * if any kind of multithreaded message processing takes place. + */ +public class MsbThreadContext { + + private static final ThreadLocal messageContext = new ThreadLocal<>(); + + public static MessageContext getMessageContext() { + return messageContext.get(); + } + + static void setMessageContext(MessageContext messageContext) { + MsbThreadContext.messageContext.set(messageContext); + } + + static void clear() { + messageContext.remove(); + } +} diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java index 96a1cc18..3bc35ca0 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java @@ -3,11 +3,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.api.AcknowledgementHandler; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.Responder; -import io.github.tcdl.msb.api.ResponderContext; -import io.github.tcdl.msb.api.ResponderServer; +import io.github.tcdl.msb.api.*; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.support.Utils; import org.apache.commons.lang3.Validate; @@ -90,6 +86,7 @@ void onResponder(ResponderContext responderContext) { Message originalMessage = responderContext.getOriginalMessage(); Object rawPayload = originalMessage.getRawPayload(); try { + MsbThreadContext.setMessageContext(responderContext); T request = Utils.convert(rawPayload, payloadTypeReference, payloadMapper); LOG.debug("[{}] Process message with id: [{}]", namespace, originalMessage.getId()); requestHandler.process(request, responderContext); @@ -99,6 +96,8 @@ void onResponder(ResponderContext responderContext) { } else { errorHandler(responderContext, e); } + } finally { + MsbThreadContext.clear(); } } diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java index 344bc07b..8939ef25 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java @@ -20,6 +20,7 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; @@ -49,9 +50,11 @@ public void setUp() { @Test public void testResponderServerProcessPayloadSuccess() throws Exception { - ResponderServer.RequestHandler, Object, Map>> - handler = (request, responderContext) -> { - }; + Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); + + ResponderServer.RequestHandler, Object, Map>> handler = + (request, responderContext) -> assertEquals("MessageContext must contain original message during message handler execution", originalMessage, + MsbThreadContext.getMessageContext().getOriginalMessage()); ArgumentCaptor subscriberCaptor = ArgumentCaptor.forClass(MessageHandler.class); ChannelManager spyChannelManager = spy(msbContext.getChannelManager()); @@ -67,10 +70,9 @@ public void testResponderServerProcessPayloadSuccess() throws Exception { verify(spyChannelManager).subscribe(anyString(), subscriberCaptor.capture()); - Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - AcknowledgementHandler mockAcknowledgeHandler = mock(AcknowledgementHandler.class); - + assertNull("MessageContext must be absent outside message handler execution", MsbThreadContext.getMessageContext()); subscriberCaptor.getValue().handleMessage(originalMessage, null); + assertNull("MessageContext must be absent outside message handler execution", MsbThreadContext.getMessageContext()); verify(spyResponderServer).onResponder(anyObject()); } diff --git a/release-notes.html b/release-notes.html index 9365907b..586a1132 100644 --- a/release-notes.html +++ b/release-notes.html @@ -14,7 +14,7 @@

March 10, 2016

Features of MSB-Java version 1.4.5: - Updated messages schema validation; - Fixed issue with unresolved host name; - - Added FlowContext to support flows that involve multiple conversations between microservices. + - Added MsbThreadContext to support flows that involve multiple conversations between microservices. Features of MSB-Java version 1.4.4: - MsbContext shutdown callbacks support; From 8bcafade1a08f4ebb5bbae6292b2043315d12b01 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Sun, 10 Apr 2016 17:47:31 +0300 Subject: [PATCH 107/226] Updated 1.4.5 release date --- release-notes.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes.html b/release-notes.html index 586a1132..db3cbbc4 100644 --- a/release-notes.html +++ b/release-notes.html @@ -6,7 +6,7 @@

Welcome to MSB-Java version 1.4.5

-

March 10, 2016

+

April 8, 2016

 

From 146a119cadec80fbfc9e926844834d490960f465 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Sun, 10 Apr 2016 18:25:05 +0300
Subject: [PATCH 108/226] [maven-release-plugin] prepare release msb-java-1.4.5

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 pom.xml            | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 9a302e21..bdbf3193 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.5-SNAPSHOT
+        1.4.5
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.5
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 3e0420f1..6d8618d0 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.5-SNAPSHOT
+        1.4.5
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.5
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 4ff720aa..ccacc267 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.5-SNAPSHOT
+        1.4.5
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.5
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index fabf5482..b4aa3fd8 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.5-SNAPSHOT
+        1.4.5
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.5
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index f082fb4a..5974c153 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.5-SNAPSHOT
+        1.4.5
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.5
     
 
     
diff --git a/pom.xml b/pom.xml
index 6454099b..b14be133 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.4.5-SNAPSHOT
+    1.4.5
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.5
     
     
         

From c62fec9384cae5d2f39aff09ad9e7279ba8b762f Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Sun, 10 Apr 2016 18:25:11 +0300
Subject: [PATCH 109/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 pom.xml            | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index bdbf3193..0482450a 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.5
+        1.4.6-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.5
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 6d8618d0..d1881371 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.5
+        1.4.6-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.5
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index ccacc267..932d3ab9 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.5
+        1.4.6-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.5
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index b4aa3fd8..5adb561f 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.5
+        1.4.6-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.5
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 5974c153..081d9a2f 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.5
+        1.4.6-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.5
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index b14be133..506e0145 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.4.5
+    1.4.6-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.5
+        HEAD
     
     
         

From dc77131ffec60906c67ad741eb112d5834eddabb Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Sun, 10 Apr 2016 19:28:38 +0300
Subject: [PATCH 110/226] Added 1.4.5 release info

---
 release-notes.html | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/release-notes.html b/release-notes.html
index db3cbbc4..b6ad5a65 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -14,6 +14,8 @@ 

April 8, 2016

Features of MSB-Java version 1.4.5: - Updated messages schema validation; - Fixed issue with unresolved host name; + - Replaced Rest like response on incorrect structure request with zero Ack; + - Added custom error handler for messages parsing or schema validation errors; - Added MsbThreadContext to support flows that involve multiple conversations between microservices. Features of MSB-Java version 1.4.4: From 6136171ab6dcdaa325822b50d14deb564ff50c18 Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 12 Apr 2016 15:31:47 +0300 Subject: [PATCH 111/226] 1.4.6 release preparation as far as the corrupted release 1.4.5 has been discarded --- release-notes.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/release-notes.html b/release-notes.html index b6ad5a65..3527c1d5 100644 --- a/release-notes.html +++ b/release-notes.html @@ -4,14 +4,14 @@ -

Welcome to MSB-Java version 1.4.5

+

Welcome to MSB-Java version 1.4.6

-

April 8, 2016

+

April 12, 2016

 
 ------------------------------------------------------------------------------------
-Features of MSB-Java version 1.4.5:
+Features of MSB-Java version 1.4.6:
    - Updated messages schema validation;
    - Fixed issue with unresolved host name;
    - Replaced Rest like response on incorrect structure request with zero Ack; 

From ddc369c42cfc13a07279a978e2c086e945ce2384 Mon Sep 17 00:00:00 2001
From: anha1 
Date: Tue, 12 Apr 2016 15:34:01 +0300
Subject: [PATCH 112/226] [maven-release-plugin] prepare release msb-java-1.4.6

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 pom.xml            | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 0482450a..46cc0846 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.6-SNAPSHOT
+        1.4.6
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.6
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index d1881371..84fac1cc 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.6-SNAPSHOT
+        1.4.6
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.6
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 932d3ab9..097b6bf1 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.6-SNAPSHOT
+        1.4.6
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.6
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 5adb561f..e8ae74ed 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.6-SNAPSHOT
+        1.4.6
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.6
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 081d9a2f..2e7b538a 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.6-SNAPSHOT
+        1.4.6
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.6
     
 
     
diff --git a/pom.xml b/pom.xml
index 506e0145..301368df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.4.6-SNAPSHOT
+    1.4.6
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.4.6
     
     
         

From 54067ad19abc08f80adc8f6e7cd8a6a16c4dccca Mon Sep 17 00:00:00 2001
From: anha1 
Date: Tue, 12 Apr 2016 15:34:08 +0300
Subject: [PATCH 113/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 pom.xml            | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 46cc0846..f553a68e 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.6
+        1.4.7-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.6
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 84fac1cc..b19b5fb0 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.6
+        1.4.7-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.6
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 097b6bf1..306d7c13 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.6
+        1.4.7-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.6
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index e8ae74ed..3022169d 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.6
+        1.4.7-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.6
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 2e7b538a..a170eba3 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.6
+        1.4.7-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.6
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index 301368df..d28530bd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.4.6
+    1.4.7-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.4.6
+        HEAD
     
     
         

From 0b2030ed8ca540fd4d69355311ab7ae972562109 Mon Sep 17 00:00:00 2001
From: rdro-tc 
Date: Mon, 16 May 2016 16:44:10 +0300
Subject: [PATCH 114/226] Fixed idle channels

---
 .../amqp/AmqpAutoRecoveringChannel.java        | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java
index b4e2e20a..46c59d3b 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java
@@ -16,7 +16,7 @@ public class AmqpAutoRecoveringChannel {
     private static final Logger LOG = LoggerFactory.getLogger(AmqpAutoRecoveringChannel.class);
 
     private AmqpConnectionManager connectionManager;
-    private Channel channel;
+    private volatile Channel channel;
 
     /**
      * Lock object used for 2 purposes:
@@ -44,7 +44,7 @@ public void basicPublish(String exchange, String routingKey, AMQP.BasicPropertie
 
     private Channel obtainChannelForPublisherConfirms() throws IOException {
         synchronized (lock) {
-            if (channel == null) {
+            if (channel == null || !channel.isOpen()) {
                 createChannelForPublisherConfirms(connectionManager);
             }
             return channel;
@@ -52,6 +52,11 @@ private Channel obtainChannelForPublisherConfirms() throws IOException {
     }
 
     private void createChannelForPublisherConfirms(AmqpConnectionManager connectionManager) throws IOException {
+
+        if (channel != null) {
+            closeChannel(channel);
+        }
+
         channel = connectionManager.obtainConnection().createChannel();
         channel.confirmSelect();
 
@@ -79,9 +84,18 @@ public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                 so the call to createChannel causes a deadlock since it blocks waiting for a response (whilst the connection's thread
                 is stuck executing the listener).
                  */
+                    closeChannel(channel);
                     channel = null;
                 }
             }
         });
     }
+
+    private void closeChannel(Channel channel) {
+        try {
+            channel.abort();
+        } catch (IOException e) {
+            LOG.info("Error closing AMQP channel", e.getCause());
+        }
+    }
 }

From d49daf976434aa928303ac0386a4eb29bf48d282 Mon Sep 17 00:00:00 2001
From: alex 
Date: Tue, 17 May 2016 13:33:16 +0300
Subject: [PATCH 115/226] API enhancement. Added bunch of overloaded request()
 methods in Requester. Added two convenience methods in ObjectFactory.

---
 .../tcdl/msb/acceptance/MsbTestHelper.java    |  18 +-
 .../bdd/steps/RequesterResponderSteps.java    | 206 +++++++++++++-----
 .../scenarios/requester_responder.story       |  29 +++
 .../io/github/tcdl/msb/api/ObjectFactory.java |  17 ++
 .../github/tcdl/msb/api/RequestOptions.java   |  13 ++
 .../io/github/tcdl/msb/api/Requester.java     |  38 ++++
 .../github/tcdl/msb/collector/Collector.java  |   6 +-
 .../io/github/tcdl/msb/config/MsbConfig.java  |  17 ++
 .../tcdl/msb/impl/ObjectFactoryImpl.java      |  39 ++++
 .../github/tcdl/msb/impl/RequesterImpl.java   |  66 +++++-
 .../github/tcdl/msb/impl/ResponderImpl.java   |   4 -
 core/src/main/resources/reference.conf        |   5 +
 .../tcdl/msb/api/RequestOptionsTest.java      |  28 +++
 .../tcdl/msb/impl/ObjectFactoryImplTest.java  |   7 +
 .../objectfactory/TestMsbObjectFactory.java   |  41 +++-
 15 files changed, 444 insertions(+), 90 deletions(-)

diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
index fc0472f7..2f1db527 100644
--- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
+++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
@@ -1,12 +1,8 @@
 package io.github.tcdl.msb.acceptance;
 
-import io.github.tcdl.msb.api.Callback;
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.MsbContext;
-import io.github.tcdl.msb.api.MsbContextBuilder;
-import io.github.tcdl.msb.api.RequestOptions;
-import io.github.tcdl.msb.api.Requester;
-import io.github.tcdl.msb.api.ResponderServer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.typesafe.config.Config;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.impl.MsbContextImpl;
@@ -14,9 +10,7 @@
 
 import java.util.HashMap;
 import java.util.Map;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.typesafe.config.Config;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * Utility to simplify using requester and responder server
@@ -141,6 +135,10 @@ public  void sendRequest(Requester requester, Object payload, boolean wait
         requester.publish(payload, tag);
     }
 
+    public  CompletableFuture sendForResult(Requester requester, Object payload, String... tags){
+        return requester.request(payload, tags);
+    }
+
     public ResponderServer createResponderServer(String namespace, ResponderServer.RequestHandler requestHandler) {
         return createResponderServer(DEFAULT_CONTEXT_NAME, namespace, requestHandler);
     }
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 08d5f163..2dabaa6c 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -1,18 +1,9 @@
 package io.github.tcdl.msb.acceptance.bdd.steps;
 
-import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import io.github.tcdl.msb.api.Requester;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.support.Utils;
-
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
-
 import org.hamcrest.Matchers;
 import org.jbehave.core.annotations.Given;
 import org.jbehave.core.annotations.Then;
@@ -21,10 +12,18 @@
 import org.jbehave.core.model.OutcomesTable;
 import org.junit.Assert;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME;
+import static org.junit.Assert.fail;
 
 /**
- * Steps to send requests and respond with predifined responses
+ * Steps to send requests and respond with predefined responses
  */
 public class RequesterResponderSteps extends MsbSteps {
 
@@ -42,6 +41,12 @@ public class RequesterResponderSteps extends MsbSteps {
     private volatile int responsesToSendCount;
     private volatile int responsesToExpectCount;
     private volatile String latestForwardNamespace = null;
+    private CompletableFuture lastFutureResult = null;
+    private RestPayload resolvedResponse = null;
+    private final LinkedList> responses = new LinkedList<>();
+    private final String ACK = "ACK";
+    private final String PAYLOAD = "PAYLOAD";
+    private final int ACK_TIMEOUT = 500;
 
     public Optional getDefaultRequestsAckType() {
         return defaultRequestsAckType;
@@ -53,53 +58,85 @@ public void createResponderServer(String namespace) {
         createResponderServer(DEFAULT_CONTEXT_NAME, namespace);
     }
 
+    @Given("responder server responds sequentially on namespace $namespace")
+    public void respondSequentially(String namespace) throws Exception {
+        ObjectMapper mapper = helper.getPayloadMapper(DEFAULT_CONTEXT_NAME);
+        helper.createResponderServer(DEFAULT_CONTEXT_NAME, namespace, (request, responderContext) -> {
+            if (responses.isEmpty()) {
+                return;
+            }
+
+            responses.forEach(nextResponse -> nextResponse.entrySet().stream().findFirst().ifPresent(entry -> {
+                switch (entry.getKey()) {
+                    case ACK:
+                        Integer responsesRemaining = (Integer) (entry.getValue());
+                        responderContext.getResponder().sendAck(ACK_TIMEOUT, responsesRemaining);
+                        try {
+                            TimeUnit.MILLISECONDS.sleep(ACK_TIMEOUT);
+                        } catch (InterruptedException e) {
+                            throw new RuntimeException(e);
+                        }
+                        break;
+                    case PAYLOAD:
+                        RestPayload payload = new RestPayload.Builder()
+                                .withBody(Utils.fromJson(responseBody, Map.class, mapper))
+                                .build();
+                        responderContext.getResponder().send(payload);
+                        break;
+                }
+            }));
+        }).listen();
+    }
+
     @Given("responder server from $contextName listens on namespace $namespace")
     @When("responder server from $contextName listens on namespace $namespace")
     public void createResponderServer(String contextName, String namespace) {
         beforeCreateResponder();
         ObjectMapper mapper = helper.getPayloadMapper(contextName);
         helper.createResponderServer(contextName, namespace, (request, responderContext) -> {
-            if (responseBody != null) {
-                countRequestsReceived.incrementAndGet();
-
-                Runnable responseActions = () -> {
-                    boolean isSendResponse = true;
-                    String ackType = nextRequestAckType.orElseGet(
-                            () -> defaultRequestsAckType.orElseGet(
-                                    () -> "auto"));
-
-                    switch (ackType) {
-                        case "confirm":
-                            responderContext.getAcknowledgementHandler().confirmMessage();
-                            break;
-                        case "reject":
-                            responderContext.getAcknowledgementHandler().rejectMessage();
-                            isSendResponse = false;
-                            break;
-                        case "retry":
-                            responderContext.getAcknowledgementHandler().retryMessage();
-                            isSendResponse = false;
-                            break;
-                    }
+            if (responseBody == null) {
+                return;
+            }
 
-                    nextRequestAckType = Optional.empty();
-                    latestForwardNamespace = responderContext.getOriginalMessage().getTopics().getForward();
-                    if (isSendResponse) {
-                        RestPayload payload = new RestPayload.Builder()
-                                .withBody(Utils.fromJson(responseBody, Map.class, mapper))
-                                .build();
-                        for(int i=0; i< responsesToSendCount; i++) {
-                            responderContext.getResponder().send(payload);
-                        }
-                    }
-                };
+            countRequestsReceived.incrementAndGet();
+
+            Runnable responseActions = () -> {
+                boolean isSendResponse = true;
+                String ackType = nextRequestAckType.orElseGet(
+                        () -> defaultRequestsAckType.orElseGet(
+                                () -> "auto"));
+
+                switch (ackType) {
+                    case "confirm":
+                        responderContext.getAcknowledgementHandler().confirmMessage();
+                        break;
+                    case "reject":
+                        responderContext.getAcknowledgementHandler().rejectMessage();
+                        isSendResponse = false;
+                        break;
+                    case "retry":
+                        responderContext.getAcknowledgementHandler().retryMessage();
+                        isSendResponse = false;
+                        break;
+                }
 
-                if(isResponseInNewThread) {
-                    responderContext.getAcknowledgementHandler().setAutoAcknowledgement(false);
-                    new Thread(responseActions).run();
-                } else {
-                    responseActions.run();
+                nextRequestAckType = Optional.empty();
+                latestForwardNamespace = responderContext.getOriginalMessage().getTopics().getForward();
+                if (isSendResponse) {
+                    RestPayload payload = new RestPayload.Builder()
+                            .withBody(Utils.fromJson(responseBody, Map.class, mapper))
+                            .build();
+                    for (int i = 0; i < responsesToSendCount; i++) {
+                        responderContext.getResponder().send(payload);
+                    }
                 }
+            };
+
+            if (isResponseInNewThread) {
+                responderContext.getAcknowledgementHandler().setAutoAcknowledgement(false);
+                new Thread(responseActions).run();
+            } else {
+                responseActions.run();
             }
         }).listen();
     }
@@ -211,7 +248,7 @@ private void onBeforeRequest() {
     }
 
     private void onResponse(RestPayload> payload) {
-        if(responseProcessingDelay > 0) {
+        if (responseProcessingDelay > 0) {
             try {
                 Thread.sleep(responseProcessingDelay);
             } catch (InterruptedException e) {
@@ -219,7 +256,7 @@ private void onResponse(RestPayload>
             }
         }
 
-        if(responseCountDown != null) {
+        if (responseCountDown != null) {
             responseCountDown.countDown();
         }
 
@@ -228,8 +265,8 @@ private void onResponse(RestPayload>
     }
 
     private void onEnd(Void in) {
-        if(responseCountDown != null && responseCountDown.getCount() > 0) {
-            Assert.fail("onEnd has been executed while not all responses were received yet, pending responses count: " + responseCountDown.getCount());
+        if (responseCountDown != null && responseCountDown.getCount() > 0) {
+            fail("onEnd has been executed while not all responses were received yet, pending responses count: " + responseCountDown.getCount());
         }
     }
 
@@ -238,15 +275,15 @@ public void waitForResponse(long timeout) throws Exception {
         try {
             receivedResponse = receivedResponseFuture.get(timeout, TimeUnit.MILLISECONDS);
         } catch (TimeoutException timeoutException) {
-            Assert.fail("Response has not been received during a timeout");
+            fail("Response has not been received during a timeout");
         }
         Assert.assertNotNull("Response received is null", receivedResponse);
     }
 
     @Then("requester will get all responses in $timeout ms")
     public void waitForAllResponses(long timeout) throws Exception {
-        if(!responseCountDown.await(timeout, TimeUnit.MILLISECONDS)) {
-            Assert.fail("All responses has not been received during a timeout, pending count: " + responseCountDown.getCount());
+        if (!responseCountDown.await(timeout, TimeUnit.MILLISECONDS)) {
+            fail("All responses has not been received during a timeout, pending count: " + responseCountDown.getCount());
         }
     }
 
@@ -298,4 +335,59 @@ public void responseContains(ExamplesTable table) throws Exception {
 
         outcomes.verify();
     }
+
+    @When("requester sends a request for single result to namespace $namespace")
+    public void requestForSingleResult(String namespace) throws Exception {
+        String query = "QUERY";
+        String body = "body";
+        RestPayload payload = helper.createFacetParserPayload(query, body);
+        requester = helper.createRequester(namespace, 1, RestPayload.class);
+        lastFutureResult = helper.sendForResult(requester, payload);
+    }
+
+    @When("requester blocks waiting for response for $timeout ms")
+    public void blockUntilResponseReceived(int timeout) throws Exception {
+        resolvedResponse = lastFutureResult.get(timeout, TimeUnit.MILLISECONDS);
+    }
+
+    @Then("resolved response equals $table")
+    public void verifyFutureResult(ExamplesTable table) {
+        Map expected = table.getRow(0);
+        OutcomesTable outcomes = new OutcomesTable();
+
+        for (String key : expected.keySet()) {
+            outcomes.addOutcome(key, resolvedResponse.getBody().toString(), Matchers.containsString(expected.get(key)));
+        }
+
+        outcomes.verify();
+    }
+
+    @Given("next response from responder contains acknowledge with $remaining remaining response")
+    public void addAckToResponsesQueue(int remainingResponses) {
+        Map request = new HashMap<>();
+        request.put(ACK, remainingResponses);
+        responses.add(request);
+    }
+
+    @Given("next response from responder contains body $responseBody")
+    public void addBodyToResponsesQueue(String responseBody) {
+        Map request = new HashMap<>();
+        request.put(PAYLOAD, responseBody);
+        responses.add(request);
+    }
+
+    @Then("requester gets exception when tries to obtain result")
+    public void assertExceptionOccured() throws Exception {
+        try {
+            resolvedResponse = lastFutureResult.get(5000, TimeUnit.MILLISECONDS);
+        } catch (CancellationException e) {
+            return;//ok
+        }
+        fail("Expected exception not thrown");
+    }
+
+    @Then("reset mock responses")
+    public void  resetMockResponses(){
+        responses.clear();
+    }
 }
diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story
index 65334e2d..576fb551 100644
--- a/acceptance/src/test/resources/scenarios/requester_responder.story
+++ b/acceptance/src/test/resources/scenarios/requester_responder.story
@@ -6,6 +6,7 @@ And clear log
 After:
 Outcome: ANY
 Then shutdown MSB
+Then reset mock responses
 
 Scenario: Sends a request to a responder server and waits for response
 
@@ -132,3 +133,31 @@ Then requester gets response in 5000 ms
 And responder requests received count equals 1
 And request forward namespace equals test:jbehave:forwarding
 
+Scenario: Requester sends request for single future response
+
+Given responder server responds with '{"result": "hello jbehave - future"}'
+And responder server listens on namespace test:jbehave
+When requester sends a request for single result to namespace test:jbehave
+And requester blocks waiting for response for 5000 ms
+Then resolved response equals
+|result|
+|hello jbehave - future|
+
+Scenario: Actual response comes after acknowledge
+
+Given next response from responder contains acknowledge with 1 remaining response
+And next response from responder contains body '{"result": "hello jbehave - future"}'
+And responder server responds sequentially on namespace test:jbehave
+When requester sends a request for single result to namespace test:jbehave
+And requester blocks waiting for response for 5000 ms
+Then resolved response equals
+|result|
+|hello jbehave - future|
+
+Scenario: To many responses
+
+Given next response from responder contains acknowledge with 2 remaining response
+And next response from responder contains body '{"result": "hello jbehave - future"}'
+And responder server responds sequentially on namespace test:jbehave
+When requester sends a request for single result to namespace test:jbehave
+Then requester gets exception when tries to obtain result
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
index a32edbc3..9c1763df 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
@@ -43,6 +43,15 @@ public Type getType() {
      */
      Requester createRequester(String namespace, RequestOptions requestOptions, TypeReference payloadTypeReference);
 
+    /**
+     * Creates requester for single response with default response and acknowledgment timeouts
+     *
+     * @param namespace             topic name to send a request to
+     * @param payloadClass  expected payload class of response messages
+     * @return new instance of a {@link Requester} with original message
+     */
+     Requester createRequesterForSingleResponse(String namespace, Class payloadClass);
+
     /**
      * Convenience method that specifies incoming payload type as {@link JsonNode}
      *
@@ -78,6 +87,14 @@ public Type getType() {
         });
     }
 
+    /**
+     * Creates requester that doesn't wait for any responses or acknowledgments
+     *
+     * @param namespace             topic name to send a request to
+     * @return new instance of a {@link Requester} with original message
+     */
+     Requester createRequesterForFireAndForget(String namespace);
+
     /**
      * @param namespace                 topic on a bus for listening on incoming requests
      * @param messageTemplate           template used for creating response messages
diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
index a30d7f21..7a73863d 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
@@ -110,6 +110,19 @@ public Builder withForwardNamespace(String forward) {
             return this;
         }
 
+        /**
+         * Convenience method to prepare Builder with properties equal to {@literal source} properties.
+         * Is useful for cases when almost same RequestOptions except one or two properties are needed.
+         */
+        public Builder from(RequestOptions source) {
+            this.ackTimeout = source.ackTimeout;
+            this.responseTimeout = source.responseTimeout;
+            this.waitForResponses = source.waitForResponses;
+            this.messageTemplate = source.messageTemplate;
+            this.forwardNamespace = source.forwardNamespace;
+            return this;
+        }
+
         public RequestOptions build() {
             return new RequestOptions(ackTimeout, responseTimeout, waitForResponses, messageTemplate, forwardNamespace);
         }
diff --git a/core/src/main/java/io/github/tcdl/msb/api/Requester.java b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
index 0b56f7e0..226f4132 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/Requester.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
@@ -5,6 +5,7 @@
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.Message;
 
+import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
 
 /**
@@ -62,6 +63,43 @@ public interface Requester {
      */
     void publish(Object requestPayload, Message originalMessage);
 
+    /**
+     * Similar to {@link Requester#publish(java.lang.Object)} but expects exactly one response.
+     * CompletableFuture response type adds a lot of flexibility to client implementation.
+     * @return {@link CompletableFuture} that will be completed when first response is received.
+     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
+     * is received.
+     */
+    CompletableFuture request(Object requestPayload);
+
+     /**
+     * Similar to {@link Requester#publish(java.lang.Object, java.lang.String...)} but expects exactly one response.
+     * CompletableFuture response type adds a lot of flexibility to client implementation.
+     * @return {@link CompletableFuture} that will be completed when first response is received.
+     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
+     * is received.
+     */
+    CompletableFuture request(Object requestPayload, String... tags);
+
+     /**
+     * Similar to {@link Requester#publish(java.lang.Object, io.github.tcdl.msb.api.message.Message)}
+     * but expects exactly one response. CompletableFuture response type adds a lot of flexibility to client implementation.
+     * @return {@link CompletableFuture} that will be completed when first response is received.
+     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
+     * is received.
+     */
+    CompletableFuture request(Object requestPayload, Message originalMessage);
+
+    /**
+     * Similar to
+     * {@link io.github.tcdl.msb.api.Requester#publish(java.lang.Object, io.github.tcdl.msb.api.message.Message, java.lang.String...)}
+     * but expects exactly one response. CompletableFuture response type adds a lot of flexibility to client implementation.
+     * @return {@link CompletableFuture} that will be completed when first response is received.
+     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
+     * is received.
+     */
+    CompletableFuture request(Object requestPayload, Message originalMessage, String... tags);
+
     /**
      * Registers a callback to be called when {@link Message} with {@link Acknowledge} part set is received.
      *
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
index df998e91..f21c71fb 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
@@ -123,11 +123,9 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt
         this.timeoutMs = getResponseTimeoutFromConfigs(requestOptions);
         this.currentTimeoutMs = timeoutMs;
 
-        int waitForResponses = requestOptions.getWaitForResponses();
+        this.responsesRemaining = requestOptions.getWaitForResponses();
 
-        this.responsesRemaining = waitForResponses;
-
-        shouldWaitUntilResponseTimeout = (waitForResponses == RequestOptions.WAIT_FOR_RESPONSES_UNTIL_TIMEOUT);
+        this.shouldWaitUntilResponseTimeout = (responsesRemaining == RequestOptions.WAIT_FOR_RESPONSES_UNTIL_TIMEOUT);
 
         this.payloadTypeReference = payloadTypeReference;
 
diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java
index bc2a7401..39b572af 100644
--- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java
+++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java
@@ -39,6 +39,10 @@ public class MsbConfig {
 
     private final String mdcLoggingSplitTagsBy;
 
+    private final int defaultResponseTimeout;
+
+    private final int defaultAckTimeout;
+
     public MsbConfig(Config loadedConfig) {
         Config config = loadedConfig.getConfig("msbConfig");
 
@@ -57,6 +61,11 @@ public MsbConfig(Config loadedConfig) {
         this.mdcLoggingSplitTagsBy = getOptionalString(mdcLogging, "splitTagsBy").orElse(null);
         this.mdcLoggingKeyMessageTags = getString(mdcLoggingMessageKeys, "messageTags");
         this.mdcLoggingKeyCorrelationId = getString(mdcLoggingMessageKeys, "correlationId");
+
+        Config requestOptionsConfig = config.getConfig("requestOptions");
+        this.defaultResponseTimeout = getInt(requestOptionsConfig, "responseTimeout");
+        this.defaultAckTimeout = getInt(requestOptionsConfig, "ackTimeout");
+
         LOG.debug("Loaded {}", this);
     }
 
@@ -113,6 +122,14 @@ public String getMdcLoggingSplitTagsBy() {
         return mdcLoggingSplitTagsBy;
     }
 
+    public int getDefaultResponseTimeout() {
+        return defaultResponseTimeout;
+    }
+
+    public int getDefaultAckTimeout() {
+        return defaultAckTimeout;
+    }
+
     @Override public String toString() {
         //please keep custom "brokerConfig" data
         return "MsbConfig{" +
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
index ade2afe1..c58d1aa7 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
@@ -10,11 +10,13 @@
 import io.github.tcdl.msb.api.ResponderServer;
 import io.github.tcdl.msb.api.monitor.AggregatorStats;
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
+import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.monitor.aggregator.DefaultChannelMonitorAggregator;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.lang.reflect.Type;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.ThreadFactory;
@@ -40,6 +42,34 @@ public  Requester createRequester(String namespace, RequestOptions request
         return RequesterImpl.create(namespace, requestOptions, msbContext, payloadTypeReference);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public  Requester createRequesterForSingleResponse(String namespace, Class payloadClass) {
+        MsbConfig msbConfig = msbContext.getMsbConfig();
+
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(1)
+                .withResponseTimeout(msbConfig.getDefaultResponseTimeout())
+                .withAckTimeout(msbConfig.getDefaultAckTimeout())
+                .build();
+
+        return RequesterImpl.create(namespace, requestOptions, msbContext, toTypeReference(payloadClass));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public  Requester createRequesterForFireAndForget(String namespace) {
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(0)
+                .build();
+
+        return RequesterImpl.create(namespace, requestOptions, msbContext, null);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -96,4 +126,13 @@ public synchronized void shutdown() {
     DefaultChannelMonitorAggregator createDefaultChannelMonitorAggregator(Callback aggregatorStatsHandler, ScheduledExecutorService scheduledExecutorService) {
         return new DefaultChannelMonitorAggregator(msbContext, scheduledExecutorService, aggregatorStatsHandler);
     }
+
+    private static  TypeReference toTypeReference(Class clazz) {
+        return new TypeReference() {
+            @Override
+            public Type getType() {
+                return clazz;
+            }
+        };
+    }
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
index be1e7fb7..b07b5480 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
@@ -2,18 +2,16 @@
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import io.github.tcdl.msb.ChannelManager;
-import io.github.tcdl.msb.api.Callback;
-import io.github.tcdl.msb.api.MessageContext;
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.RequestOptions;
-import io.github.tcdl.msb.api.Requester;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.collector.Collector;
 import io.github.tcdl.msb.events.EventHandlers;
 import io.github.tcdl.msb.message.MessageFactory;
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.Validate;
 
+import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
 
 /**
@@ -64,7 +62,7 @@ private RequesterImpl(String namespace, RequestOptions requestOptions, MsbContex
      */
     @Override
     public void publish(Object requestPayload) {
-        publish(requestPayload, null, null);
+        publish(requestPayload, null, ArrayUtils.EMPTY_STRING_ARRAY);
     }
 
     /**
@@ -80,7 +78,57 @@ public void publish(Object requestPayload, String... tags) {
      */
     @Override
     public void publish(Object requestPayload, Message originalMessage) {
-        publish(requestPayload, originalMessage, null);
+        publish(requestPayload, originalMessage, ArrayUtils.EMPTY_STRING_ARRAY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CompletableFuture request(Object requestPayload) {
+        return request(requestPayload, null, ArrayUtils.EMPTY_STRING_ARRAY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CompletableFuture request(Object requestPayload, String... tags) {
+        return request(requestPayload, null, tags);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CompletableFuture request(Object requestPayload, Message originalMessage) {
+        return request(requestPayload, originalMessage, ArrayUtils.EMPTY_STRING_ARRAY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CompletableFuture request(Object requestPayload, Message originalMessage, String... tags) {
+        CompletableFuture futureResult = new CompletableFuture<>();
+
+        this.onResponse((response, messageContext) -> futureResult.complete(response))
+                .onAcknowledge((acknowledge, messageContext) -> {
+                    boolean noResponse = !futureResult.isDone() && acknowledge.getResponsesRemaining() < 1;
+                    boolean tooManyResponses = acknowledge.getResponsesRemaining() > 1;
+                    if (noResponse || tooManyResponses) {
+                        futureResult.cancel(true);
+                    }
+                })
+                .onEnd(end -> {
+                    if (!futureResult.isDone()) {
+                        futureResult.cancel(true);
+                    }
+                })
+                .onError((exception, message) -> futureResult.cancel(true));
+
+        publish(requestOptions, requestPayload, originalMessage, tags);
+        return futureResult;
     }
 
     /**
@@ -88,6 +136,10 @@ public void publish(Object requestPayload, Message originalMessage) {
      */
     @Override
     public void publish(Object requestPayload, Message originalMessage, String... tags) {
+        publish(requestOptions, requestPayload, originalMessage, tags);
+    }
+
+    private void publish(RequestOptions requestOptions, Object requestPayload, Message originalMessage, String... tags) {
         MessageTemplate messageTemplate = MessageTemplate.copyOf(requestOptions.getMessageTemplate());
         if (tags != null) {
             for(String tag: tags) {
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java
index 17a7ec66..42715a7a 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java
@@ -19,18 +19,14 @@ public class ResponderImpl implements Responder {
     private static final Logger LOG = LoggerFactory.getLogger(ResponderImpl.class);
 
     private String responderId;
-    private Message originalMessage;
     private ChannelManager channelManager;
     private MessageFactory messageFactory;
     private Message.Builder messageBuilder;
-    private AcknowledgementHandler ackHandler;
 
     public ResponderImpl(MessageTemplate messageTemplate, Message originalMessage, 
             MsbContextImpl msbContext) {
         validateReceivedMessage(originalMessage);
         this.responderId = Utils.generateId();
-        this.originalMessage = originalMessage;
-        this.ackHandler = ackHandler;
         this.channelManager = msbContext.getChannelManager();
         this.messageFactory = msbContext.getMessageFactory();
         this.messageBuilder = messageFactory.createResponseMessageBuilder(messageTemplate, originalMessage);
diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf
index 0b4ded99..471df44a 100644
--- a/core/src/main/resources/reference.conf
+++ b/core/src/main/resources/reference.conf
@@ -29,5 +29,10 @@ msbConfig {
       correlationId = "msbCorrelationId"
     }
   }
+
+  requestOptions {
+    responseTimeout = 5000
+    ackTimeout = 5000
+  }
 }
 
diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
index e3392152..bc8fd35d 100644
--- a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
@@ -1,6 +1,7 @@
 package io.github.tcdl.msb.api;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
 
 import org.junit.Test;
 
@@ -44,4 +45,31 @@ public void testForwardNamespace() {
         assertEquals(forwardNamespace, requestOptions.getForwardNamespace());
     }
 
+    @Test
+    public void testBuilderFromExistingRequestOptions() throws Exception {
+
+        MessageTemplate sourceMessageTemplate = new MessageTemplate();
+
+        Integer ackTimeout = 1;
+        Integer responseTimeout = 2;
+        String forwardNamespace = "forward:namespace";
+        int waitForResponses = 3;
+
+        RequestOptions source = new RequestOptions.Builder()
+                .withAckTimeout(ackTimeout)
+                .withResponseTimeout(responseTimeout)
+                .withForwardNamespace(forwardNamespace)
+                .withWaitForResponses(waitForResponses)
+                .withMessageTemplate(sourceMessageTemplate)
+                .build();
+
+        RequestOptions.Builder builder = new RequestOptions.Builder().from(source);
+        RequestOptions result = builder.build();
+
+        assertEquals(ackTimeout, result.getAckTimeout());
+        assertEquals(responseTimeout, result.getResponseTimeout());
+        assertEquals(waitForResponses, result.getWaitForResponses());
+        assertEquals(forwardNamespace, result.getForwardNamespace());
+        assertSame(sourceMessageTemplate, result.getMessageTemplate());
+    }
 }
\ No newline at end of file
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java
index 748d1fa7..7589faf6 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java
@@ -77,4 +77,11 @@ public void getPayloadConverter() {
         PayloadConverter payloadConverter = objectFactory.getPayloadConverter();
         assertNotNull(payloadConverter);
     }
+
+    @Test
+    public void testCreateFireAndForgetRequester() throws Exception {
+        ObjectFactory objectFactory = new ObjectFactoryImpl(TestUtils.createMsbContextBuilder().build());
+        Requester expectedRequester = objectFactory.createRequesterForFireAndForget(NAMESPACE);
+        assertNotNull(expectedRequester);
+    }
 }
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
index 7b513994..c6475832 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
@@ -2,16 +2,12 @@
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.JsonNode;
-import io.github.tcdl.msb.api.Callback;
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.ObjectFactory;
-import io.github.tcdl.msb.api.PayloadConverter;
-import io.github.tcdl.msb.api.RequestOptions;
-import io.github.tcdl.msb.api.Requester;
-import io.github.tcdl.msb.api.ResponderServer;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.monitor.AggregatorStats;
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
 
+import java.lang.reflect.Type;
+
 /**
  * {@link ObjectFactory} implementation that captures all requesters/responders params and callbacks to be
  * used during testing.
@@ -31,6 +27,17 @@ public  Requester createRequester(String namespace, RequestOptions request
         return capture.getRequesterMock();
     }
 
+    @Override
+    public  Requester createRequesterForSingleResponse(String namespace, Class payloadClass) {
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(1)
+                .withResponseTimeout(5000)
+                .withAckTimeout(5000)
+                .build();
+
+        return createRequester(namespace, requestOptions, toTypeReference(payloadClass));
+    }
+
     @Override
     public Requester createRequester(String namespace, RequestOptions requestOptions) {
         RequesterCapture capture = new RequesterCapture<>(namespace, requestOptions, null, null);
@@ -77,13 +84,22 @@ public  ResponderServer createResponderServer(String namespace, MessageTempla
         return capture.getResponderServerMock();
     }
 
-    @Override public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
+    @Override
+    public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) {
         ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, errorHandler, null, payloadClass);
         storage.addCapture(capture);
         return capture.getResponderServerMock();
     }
 
+    @Override
+    public  Requester createRequesterForFireAndForget(String namespace) {
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(0)
+                .build();
+        return createRequester(namespace, requestOptions, (Class)null);
+    }
+
     @Override
     public PayloadConverter getPayloadConverter() {
         return null;
@@ -99,4 +115,13 @@ public void shutdown() {
 
     }
 
+    private static  TypeReference toTypeReference(Class clazz) {
+        return new TypeReference() {
+            @Override
+            public Type getType() {
+                return clazz;
+            }
+        };
+    }
+
 }

From 98e1227276632d450926ceeeb24aeca3d5ff0681 Mon Sep 17 00:00:00 2001
From: alex 
Date: Tue, 17 May 2016 13:53:06 +0300
Subject: [PATCH 116/226] API enhancement. Added bunch of overloaded request()
 methods in Requester. Added two convenience methods in ObjectFactory.

---
 .../src/test/resources/scenarios/requester_responder.story    | 3 ++-
 .../tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java     | 4 ++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story
index 576fb551..01b1d0ab 100644
--- a/acceptance/src/test/resources/scenarios/requester_responder.story
+++ b/acceptance/src/test/resources/scenarios/requester_responder.story
@@ -3,10 +3,11 @@ Before:
 Given MSB configuration with consumer prefetch count 20
 And start MSB
 And clear log
+And reset mock responses
 After:
 Outcome: ANY
 Then shutdown MSB
-Then reset mock responses
+
 
 Scenario: Sends a request to a responder server and waits for response
 
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
index c6475832..9a299cdf 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
@@ -31,8 +31,8 @@ public  Requester createRequester(String namespace, RequestOptions request
     public  Requester createRequesterForSingleResponse(String namespace, Class payloadClass) {
         RequestOptions requestOptions = new RequestOptions.Builder()
                 .withWaitForResponses(1)
-                .withResponseTimeout(5000)
-                .withAckTimeout(5000)
+                .withResponseTimeout(100)
+                .withAckTimeout(100)
                 .build();
 
         return createRequester(namespace, requestOptions, toTypeReference(payloadClass));

From 28ace105ade76978459e3efc685d3bda0f748b75 Mon Sep 17 00:00:00 2001
From: alex 
Date: Thu, 19 May 2016 12:21:54 +0300
Subject: [PATCH 117/226] API enhancement. Added
 ExecutionOptionsAwareMessageHandler interface and implemented direct
 invocation in Consumer

---
 .../java/io/github/tcdl/msb/Consumer.java     |   7 +-
 .../io/github/tcdl/msb/api/Requester.java     |  36 ++--
 .../github/tcdl/msb/collector/Collector.java  |  15 +-
 .../ExecutionOptionsAwareMessageHandler.java  |  16 ++
 .../github/tcdl/msb/impl/RequesterImpl.java   |  14 +-
 .../java/io/github/tcdl/msb/ConsumerTest.java |  48 +++--
 .../tcdl/msb/collector/CollectorTest.java     |   1 +
 .../tcdl/msb/impl/RequesterImplTest.java      | 187 +++++++++++++++---
 8 files changed, 251 insertions(+), 73 deletions(-)
 create mode 100644 core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java

diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java
index 3b1bbddf..0e52b20f 100644
--- a/core/src/main/java/io/github/tcdl/msb/Consumer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java
@@ -6,6 +6,7 @@
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.MetaMessage;
 import io.github.tcdl.msb.collector.ConsumedMessagesAwareMessageHandler;
+import io.github.tcdl.msb.collector.ExecutionOptionsAwareMessageHandler;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent;
 import io.github.tcdl.msb.support.JsonValidator;
@@ -133,7 +134,11 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern
                     consumedMessagesAwareMessageHandler = ((ConsumedMessagesAwareMessageHandler) messageHandler);
                     consumedMessagesAwareMessageHandler.notifyMessageConsumed();
                 }
-                messageHandlerInvokeStrategy.execute(messageHandler, message, acknowledgeHandler);
+                if (messageHandler instanceof ExecutionOptionsAwareMessageHandler && ((ExecutionOptionsAwareMessageHandler) messageHandler).isDirectlyInvokable()) {
+                    messageHandler.handleMessage(message,acknowledgeHandler);
+                } else {
+                    messageHandlerInvokeStrategy.execute(messageHandler, message, acknowledgeHandler);
+                }
             } else {
                 LOG.warn("{} Cant't resolve message handler for a message: {}", loggingTag, jsonMessage);
                 acknowledgeHandler.autoReject();
diff --git a/core/src/main/java/io/github/tcdl/msb/api/Requester.java b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
index 226f4132..41f9ad4d 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/Requester.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
@@ -64,29 +64,20 @@ public interface Requester {
     void publish(Object requestPayload, Message originalMessage);
 
     /**
-     * Similar to {@link Requester#publish(java.lang.Object)} but expects exactly one response.
-     * CompletableFuture response type adds a lot of flexibility to client implementation.
-     * @return {@link CompletableFuture} that will be completed when first response is received.
-     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
-     * is received.
+     * Overloaded version of
+     * {@link Requester#request(java.lang.Object, io.github.tcdl.msb.api.message.Message, java.lang.String...)}
      */
     CompletableFuture request(Object requestPayload);
 
-     /**
-     * Similar to {@link Requester#publish(java.lang.Object, java.lang.String...)} but expects exactly one response.
-     * CompletableFuture response type adds a lot of flexibility to client implementation.
-     * @return {@link CompletableFuture} that will be completed when first response is received.
-     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
-     * is received.
+    /**
+     * Overloaded version of
+     * {@link Requester#request(java.lang.Object, io.github.tcdl.msb.api.message.Message, java.lang.String...)}
      */
     CompletableFuture request(Object requestPayload, String... tags);
 
-     /**
-     * Similar to {@link Requester#publish(java.lang.Object, io.github.tcdl.msb.api.message.Message)}
-     * but expects exactly one response. CompletableFuture response type adds a lot of flexibility to client implementation.
-     * @return {@link CompletableFuture} that will be completed when first response is received.
-     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
-     * is received.
+    /**
+     * Overloaded version of
+     * {@link Requester#request(java.lang.Object, io.github.tcdl.msb.api.message.Message, java.lang.String...)}
      */
     CompletableFuture request(Object requestPayload, Message originalMessage);
 
@@ -94,6 +85,17 @@ public interface Requester {
      * Similar to
      * {@link io.github.tcdl.msb.api.Requester#publish(java.lang.Object, io.github.tcdl.msb.api.message.Message, java.lang.String...)}
      * but expects exactly one response. CompletableFuture response type adds a lot of flexibility to client implementation.
+
+     * All handlers passed to
+     * 
    + *
  • {@link Requester#onAcknowledge(java.util.function.BiConsumer)}
  • + *
  • {@link Requester#onResponse(java.util.function.BiConsumer)}
  • + *
  • {@link Requester#onRawResponse(java.util.function.BiConsumer)}
  • + *
  • {@link Requester#onEnd(io.github.tcdl.msb.api.Callback)}
  • + *
  • {@link Requester#onError(java.util.function.BiConsumer)}
  • + *
+ * are DISCARDED + * * @return {@link CompletableFuture} that will be completed when first response is received. * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses * is received. diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index f21c71fb..7bf7a9cb 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -36,7 +36,7 @@ /** * {@link Collector} is a component which collects responses and acknowledgements for sent requests. */ -public class Collector implements ConsumedMessagesAwareMessageHandler { +public class Collector implements ConsumedMessagesAwareMessageHandler, ExecutionOptionsAwareMessageHandler { private static final Logger LOG = LoggerFactory.getLogger(Collector.class); @@ -101,8 +101,15 @@ public class Collector implements ConsumedMessagesAwareMessageHandler { */ private volatile boolean isOnEndInvoked = false; + private final boolean directlyInvokable; + public Collector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl msbContext, EventHandlers eventHandlers, TypeReference payloadTypeReference) { + this(topic, requestMessage, requestOptions, msbContext, eventHandlers, payloadTypeReference, false); + } + + public Collector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl msbContext, EventHandlers eventHandlers, + TypeReference payloadTypeReference, boolean directlyInvokableCallbacks) { this.requestMessage = requestMessage; this.clock = msbContext.getClock(); @@ -134,6 +141,7 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt onAcknowledge = Optional.ofNullable(eventHandlers.onAcknowledge()); onEnd = Optional.ofNullable(eventHandlers.onEnd()); onError = Optional.ofNullable(eventHandlers.onError()); + this.directlyInvokable = directlyInvokableCallbacks; } @Override @@ -407,4 +415,9 @@ List getPayloadMessages() { Message getRequestMessage() { return requestMessage; } + + @Override + public boolean isDirectlyInvokable(){ + return directlyInvokable; + } } diff --git a/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java b/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java new file mode 100644 index 00000000..09ef6b08 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java @@ -0,0 +1,16 @@ +package io.github.tcdl.msb.collector; + +import io.github.tcdl.msb.MessageHandler; + +/** + * Created by Alexandr Zolotov + * 19.05.16 + */ +public interface ExecutionOptionsAwareMessageHandler extends MessageHandler { + + /** + * Indicates whether handler should be executed by main message handling thread (true is so) or by thread from + * consumer thread pool. + */ + boolean isDirectlyInvokable(); +} \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java index b07b5480..618b9f2f 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java @@ -110,6 +110,8 @@ public CompletableFuture request(Object requestPayload, Message originalMessa */ @Override public CompletableFuture request(Object requestPayload, Message originalMessage, String... tags) { + this.eventHandlers = new EventHandlers<>(); //discard all previously set handlers + CompletableFuture futureResult = new CompletableFuture<>(); this.onResponse((response, messageContext) -> futureResult.complete(response)) @@ -127,7 +129,7 @@ public CompletableFuture request(Object requestPayload, Message originalMessa }) .onError((exception, message) -> futureResult.cancel(true)); - publish(requestOptions, requestPayload, originalMessage, tags); + publish(true, requestOptions, requestPayload, originalMessage, tags); return futureResult; } @@ -136,10 +138,10 @@ public CompletableFuture request(Object requestPayload, Message originalMessa */ @Override public void publish(Object requestPayload, Message originalMessage, String... tags) { - publish(requestOptions, requestPayload, originalMessage, tags); + publish(false, requestOptions, requestPayload, originalMessage, tags); } - private void publish(RequestOptions requestOptions, Object requestPayload, Message originalMessage, String... tags) { + private void publish(boolean invokeHandlersDirectly, RequestOptions requestOptions, Object requestPayload, Message originalMessage, String... tags) { MessageTemplate messageTemplate = MessageTemplate.copyOf(requestOptions.getMessageTemplate()); if (tags != null) { for(String tag: tags) { @@ -160,7 +162,7 @@ private void publish(RequestOptions requestOptions, Object requestPayload, Messa if (isWaitForAckMs() || isWaitForResponses()) { String topic = message.getTopics().getResponse(); - Collector collector = createCollector(topic, message, requestOptions, context, eventHandlers); + Collector collector = createCollector(topic, message, requestOptions, context, eventHandlers, invokeHandlersDirectly); collector.listenForResponses(); getChannelManager().findOrCreateProducer(message.getTopics().getTo()) @@ -229,7 +231,7 @@ private ChannelManager getChannelManager() { return context.getChannelManager(); } - Collector createCollector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl context, EventHandlers eventHandlers) { - return new Collector<>(topic, requestMessage, requestOptions, context, eventHandlers, payloadTypeReference); + Collector createCollector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl context, EventHandlers eventHandlers, boolean invokeHandlersDirectly) { + return new Collector<>(topic, requestMessage, requestOptions, context, eventHandlers, payloadTypeReference, invokeHandlersDirectly); } } diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index e58d5c14..57c4f742 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -1,41 +1,40 @@ package io.github.tcdl.msb; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; - -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.typesafe.config.ConfigFactory; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.MetaMessage; import io.github.tcdl.msb.api.message.Topics; +import io.github.tcdl.msb.collector.Collector; import io.github.tcdl.msb.collector.ConsumedMessagesAwareMessageHandler; import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; - -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; -import java.util.Map; -import java.util.Optional; - import org.apache.commons.lang3.StringUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.typesafe.config.ConfigFactory; import org.slf4j.MDC; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.util.Map; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + @RunWith(MockitoJUnitRunner.class) public class ConsumerTest { @@ -192,6 +191,23 @@ public void testMessageHandlerCantBeResolved() throws JsonConversionException { verify(acknowledgementHandlerMock, times(1)).autoReject(); } + @Test + public void testHandleResponse_directlyInvokableHandler() throws Exception { + Collector directlyInvokableHandler = mock(Collector.class); + when(directlyInvokableHandler.isDirectlyInvokable()).thenReturn(true); + + messageHandlerMock = directlyInvokableHandler; + Message responseMessage = TestUtils.createSimpleResponseMessage(TOPIC); + + when(messageHandlerResolverMock.resolveMessageHandler(any(Message.class))).thenReturn(Optional.of(directlyInvokableHandler)); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + + consumer.handleRawMessage(Utils.toJson(responseMessage, messageMapper), acknowledgementHandlerMock); + + verify(directlyInvokableHandler).handleMessage(any(Message.class), eq(acknowledgementHandlerMock)); + verifyZeroInteractions(messageHandlerInvokeStrategyMock); + } + @Test public void testMessageHandlerInvokeException() throws JsonConversionException { doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokeStrategyMock).execute(any(), any(), any()); diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java index 6eefc704..5ee34d7e 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java @@ -186,6 +186,7 @@ MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandle assertFalse(collector.getAckMessages().contains(responseMessage)); } + @Test public void testHandleResponseConversionFailed() { String bodyText = "some body"; Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index b29f40ba..9232f919 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -9,6 +9,7 @@ import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Requester; +import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.collector.Collector; @@ -20,18 +21,14 @@ import org.mockito.runners.MockitoJUnitRunner; import java.time.Clock; +import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; +import static io.github.tcdl.msb.support.TestUtils.createPayloadWithTextBody; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertArrayEquals; -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.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -62,7 +59,7 @@ public class RequesterImplTest { @Test public void testPublishNoWaitForResponses() throws Exception { - RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null, null, null, null); publishByAllMethods(requester); @@ -72,7 +69,7 @@ public void testPublishNoWaitForResponses() throws Exception { @Test public void testPublishWaitForResponses() throws Exception { - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); publishByAllMethods(requester); @@ -90,7 +87,7 @@ private void publishByAllMethods(RequesterImpl requester) { @Test public void testPublishWaitForResponsesAck() throws Exception { - RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, null, null, arg -> fail()); + RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, null, null, null, arg -> fail()); requester.publish(TestUtils.createSimpleRequestPayload()); @@ -103,7 +100,7 @@ public void testPublishWaitForResponsesAck() throws Exception { public void testPublishHandleErrorResponse() throws Exception { RuntimeException ex = new RuntimeException(); BiConsumer errorHandlerMock = mock(BiConsumer.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, (p, c) -> { throw ex; }, errorHandlerMock, arg -> fail()); + RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, (p, c) -> { throw ex; }, null, errorHandlerMock, arg -> fail()); requester.publish(TestUtils.createSimpleRequestPayload()); Message responseMessage = TestUtils.createMsbRequestMessage("some:topic", "body text"); @@ -114,9 +111,9 @@ public void testPublishHandleErrorResponse() throws Exception { @Test public void testProducerPublishWithPayload() throws Exception { String bodyText = "Body text"; - RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null, null, null, null); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); - RestPayload payload = TestUtils.createPayloadWithTextBody(bodyText); + RestPayload payload = createPayloadWithTextBody(bodyText); requester.publish(payload); @@ -128,7 +125,7 @@ public void testProducerPublishWithPayload() throws Exception { @SuppressWarnings("unchecked") public void testAcknowledgeEventHandlerIsAdded() throws Exception { BiConsumer onAckMock = mock(BiConsumer.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); requester.onAcknowledge(onAckMock); @@ -143,7 +140,7 @@ public void testAcknowledgeEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testResponseEventHandlerIsAdded() throws Exception { BiConsumer onResponseMock = mock(BiConsumer.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); requester.onResponse(onResponseMock); @@ -158,7 +155,7 @@ public void testResponseEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testRawResponseEventHandlerIsAdded() throws Exception { BiConsumer onRawResponseMock = mock(BiConsumer.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); requester.onRawResponse(onRawResponseMock); @@ -173,7 +170,7 @@ public void testRawResponseEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testEndEventHandlerIsAdded() throws Exception { Callback onEndMock = mock(Callback.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0,null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); requester.onEnd(onEndMock); @@ -188,7 +185,7 @@ public void testEndEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testErrorEventHandlerIsAdded() throws Exception { BiConsumer onErrorMock = mock(BiConsumer.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0,null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); requester.onError(onErrorMock); @@ -203,7 +200,7 @@ public void testErrorEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testNoEventHandlerAdded() throws Exception { Callback onEndMock = mock(Callback.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); assertThat(requester.eventHandlers.onAcknowledge(), not(onEndMock)); assertThat(requester.eventHandlers.onResponse(), not(onEndMock)); @@ -212,6 +209,128 @@ public void testNoEventHandlerAdded() throws Exception { assertThat(requester.eventHandlers.onError(), not(onEndMock)); } + @SuppressWarnings("unchecked") + @Test + public void testRequest_customHandlersAreDiscarded() throws Exception { + + BiConsumer customOnResponseHandler = mock(BiConsumer.class); + BiConsumer customOnRawResponseHandler = mock(BiConsumer.class); + BiConsumer customOnAcknowledgeHandler = mock(BiConsumer.class); + BiConsumer customOnErrorHandler = mock(BiConsumer.class); + Callback customOnEndHandler = mock(Callback.class); + + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, customOnResponseHandler, customOnAcknowledgeHandler, customOnErrorHandler, customOnEndHandler); + + requester.onRawResponse(customOnRawResponseHandler); + + requester.request(TestUtils.createSimpleRequestPayload()); + + assertThat(requester.eventHandlers.onAcknowledge(), not(customOnAcknowledgeHandler)); + assertThat(requester.eventHandlers.onResponse(), not(customOnResponseHandler)); + assertThat(requester.eventHandlers.onRawResponse(), not(customOnRawResponseHandler)); + assertThat(requester.eventHandlers.onEnd(), not(customOnEndHandler)); + assertThat(requester.eventHandlers.onError(), not(customOnErrorHandler)); + } + + @Test + public void testRequest_responseHandlerCompletesFuture() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + assertFalse(futureResult.isDone()); + + RestPayload mockResponsePayload = mock(RestPayload.class); + MessageContext mockMessageContext = mock(MessageContext.class); + + requester.eventHandlers.onResponse().accept(mockResponsePayload, mockMessageContext); + assertTrue(futureResult.isDone()); + assertEquals(mockResponsePayload, futureResult.get()); + } + + @Test + public void testRequest_rawResponseHandlerDoesNotCompleteFuture() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + assertFalse(futureResult.isDone()); + + Message responseMessage = TestUtils.createSimpleResponseMessage("anyNamespace"); + MessageContext mockMessageContext = mock(MessageContext.class); + + requester.eventHandlers.onRawResponse().accept(responseMessage, mockMessageContext); + assertFalse(futureResult.isDone()); + } + + @Test + public void testRequest_errorHandlerCancelsFuture() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + assertFalse(futureResult.isDone()); + + Message responseMessage = TestUtils.createSimpleResponseMessage("anyNamespace"); + Exception e = new Exception("some message"); + + requester.eventHandlers.onError().accept(e, responseMessage); + assertTrue(futureResult.isCancelled()); + } + + @Test + public void testRequest_endHandlerCancelsNotCompletedFuture() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + assertFalse(futureResult.isDone()); + + requester.eventHandlers.onEnd().call(null); + assertTrue(futureResult.isCancelled()); + } + + @Test + public void testRequest_endHandlerDoesNothingWithCompletedFuture() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + RestPayload mockResponsePayload = mock(RestPayload.class); + MessageContext mockMessageContext = mock(MessageContext.class); + + requester.eventHandlers.onResponse().accept(mockResponsePayload, mockMessageContext); + requester.eventHandlers.onEnd().call(null); + assertFalse(futureResult.isCancelled()); + } + + @Test + public void testRequest_acknowledgeHandlerCancelsFutureOnNoResponses() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + MessageContext mockMessageContext = mock(MessageContext.class); + Acknowledge acknowledge = new Acknowledge.Builder() + .withResponderId("responderId") + .withResponsesRemaining(0) + .withTimeoutMs(0) + .build(); + + requester.eventHandlers.onAcknowledge().accept(acknowledge, mockMessageContext); + assertTrue(futureResult.isCancelled()); + } + + @Test + public void testRequest_acknowledgeHandlerCancelsFutureOnTooManyResponses() throws Exception{ + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + MessageContext mockMessageContext = mock(MessageContext.class); + Acknowledge acknowledge = new Acknowledge.Builder() + .withResponderId("responderId") + .withResponsesRemaining(2) + .withTimeoutMs(0) + .build(); + + requester.eventHandlers.onAcknowledge().accept(acknowledge, mockMessageContext); + assertTrue(futureResult.isCancelled()); + } + @Test public void testRequestMessage() throws Exception { ChannelManager channelManagerMock = mock(ChannelManager.class); @@ -225,7 +344,7 @@ public void testRequestMessage() throws Exception { .build(); RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); - Requester requester = RequesterImpl.create(NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference() {}); + Requester requester = RequesterImpl.create(NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference()); requester.publish(requestPayload); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -254,7 +373,7 @@ public void testRequestMessageWithTags() throws Exception { RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); RequestOptions requestOptions = TestUtils.createSimpleRequestOptionsWithTags(tag); - Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() {}); + Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference()); requester.publish(requestPayload, dynamicTag1, dynamicTag2, nullTag); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -280,7 +399,8 @@ public void testRequestMessageWithForward() throws Exception { .Builder() .withForwardNamespace(forwardNamespace).build(); - Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() {}); + Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() { + }); requester.publish(requestPayload); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -288,10 +408,10 @@ public void testRequestMessageWithForward() throws Exception { assertEquals(forwardNamespace, requestMessage.getTopics().getForward()); } - private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout, Integer ackTimeout, - BiConsumer onResponse, - BiConsumer onError, - Callback endHandler) throws Exception { + private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout, Integer ackTimeout, + BiConsumer onResponse, BiConsumer onAcknowledge, + BiConsumer onError, + Callback endHandler) throws Exception { MessageTemplate messageTemplateMock = mock(MessageTemplate.class); @@ -308,17 +428,20 @@ private RequesterImpl initRequesterForResponsesWith(Integer numberO .withChannelManager(channelManagerMock) .build(); - RequesterImpl requester = spy(RequesterImpl.create(NAMESPACE, requestOptionsMock, msbContext, new TypeReference() {})); + RequesterImpl requester = spy(RequesterImpl.create(NAMESPACE, requestOptionsMock, msbContext, new TypeReference() { + })); requester.onResponse(onResponse) - .onError(onError) - .onEnd(endHandler); + .onError(onError) + .onAcknowledge(onAcknowledge) + .onEnd(endHandler); collectorMock = spy(new Collector<>(NAMESPACE, TestUtils.createMsbRequestMessageNoPayload(NAMESPACE), requestOptionsMock, msbContext, requester.eventHandlers, - new TypeReference() {})); + new TypeReference() { + })); doReturn(collectorMock) .when(requester) - .createCollector(anyString(), any(Message.class), any(RequestOptions.class), any(MsbContextImpl.class), any()); + .createCollector(anyString(), any(Message.class), any(RequestOptions.class), any(MsbContextImpl.class), any(), anyBoolean()); return requester; } From 510528c45f19468f90b78a9c061715502845e1b3 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 19 May 2016 12:22:58 +0300 Subject: [PATCH 118/226] API enhancement. Added ExecutionOptionsAwareMessageHandler interface and implemented direct invocation in Consumer --- .../test/java/io/github/tcdl/msb/impl/RequesterImplTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index 9232f919..13c89bbc 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -344,7 +344,7 @@ public void testRequestMessage() throws Exception { .build(); RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); - Requester requester = RequesterImpl.create(NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference()); + Requester requester = RequesterImpl.create(NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference(){}); requester.publish(requestPayload); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -373,7 +373,7 @@ public void testRequestMessageWithTags() throws Exception { RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); RequestOptions requestOptions = TestUtils.createSimpleRequestOptionsWithTags(tag); - Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference()); + Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference(){}); requester.publish(requestPayload, dynamicTag1, dynamicTag2, nullTag); verify(producerMock).publish(messageArgumentCaptor.capture()); From 897a66bf85dc9921f9744d040db5fe676d3c4a8d Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 20 May 2016 13:56:27 +0300 Subject: [PATCH 119/226] MsbThreadContext#setMessageContex() made public --- .../src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java b/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java index c1d5546b..4246a6b9 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java @@ -15,7 +15,7 @@ public static MessageContext getMessageContext() { return messageContext.get(); } - static void setMessageContext(MessageContext messageContext) { + public static void setMessageContext(MessageContext messageContext) { MsbThreadContext.messageContext.set(messageContext); } From d1e3c1a7083d791fe377b8bc718efdc705f065df Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 20 May 2016 15:46:12 +0300 Subject: [PATCH 120/226] Added overloaded version of ObjectFactory.createRequesterForSingleResponse --- .../java/io/github/tcdl/msb/api/ObjectFactory.java | 10 +++++++++- .../java/io/github/tcdl/msb/api/RequestOptions.java | 2 +- .../java/io/github/tcdl/msb/config/MsbConfig.java | 7 ------- .../io/github/tcdl/msb/impl/ObjectFactoryImpl.java | 12 +++++++++--- core/src/main/resources/reference.conf | 1 - .../msb/mock/objectfactory/TestMsbObjectFactory.java | 9 +++++++-- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java index 9c1763df..1a1a6ec9 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java +++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java @@ -43,14 +43,22 @@ public Type getType() { */ Requester createRequester(String namespace, RequestOptions requestOptions, TypeReference payloadTypeReference); + /** + * Same as + * {@link io.github.tcdl.msb.api.ObjectFactory#createRequesterForSingleResponse(java.lang.String, java.lang.Class, int)} + * with default timeout + */ + Requester createRequesterForSingleResponse(String namespace, Class payloadClass); + /** * Creates requester for single response with default response and acknowledgment timeouts * * @param namespace topic name to send a request to * @param payloadClass expected payload class of response messages + * @param timeout response timeout (in milliseconds) * @return new instance of a {@link Requester} with original message */ - Requester createRequesterForSingleResponse(String namespace, Class payloadClass); + Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout); /** * Convenience method that specifies incoming payload type as {@link JsonNode} diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java index 7a73863d..332e0d3f 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java +++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java @@ -8,7 +8,7 @@ public class RequestOptions { public static final int WAIT_FOR_RESPONSES_UNTIL_TIMEOUT = -1; /** - * Min time (in milliseconds) to wait for acknowledgements. + * Max time (in milliseconds) to wait for acknowledgements. */ private final Integer ackTimeout; diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java index 39b572af..9f137d99 100644 --- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java +++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java @@ -41,8 +41,6 @@ public class MsbConfig { private final int defaultResponseTimeout; - private final int defaultAckTimeout; - public MsbConfig(Config loadedConfig) { Config config = loadedConfig.getConfig("msbConfig"); @@ -64,7 +62,6 @@ public MsbConfig(Config loadedConfig) { Config requestOptionsConfig = config.getConfig("requestOptions"); this.defaultResponseTimeout = getInt(requestOptionsConfig, "responseTimeout"); - this.defaultAckTimeout = getInt(requestOptionsConfig, "ackTimeout"); LOG.debug("Loaded {}", this); } @@ -126,10 +123,6 @@ public int getDefaultResponseTimeout() { return defaultResponseTimeout; } - public int getDefaultAckTimeout() { - return defaultAckTimeout; - } - @Override public String toString() { //please keep custom "brokerConfig" data return "MsbConfig{" + diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java index c58d1aa7..ce7fca67 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java @@ -48,13 +48,19 @@ public Requester createRequester(String namespace, RequestOptions request @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass) { MsbConfig msbConfig = msbContext.getMsbConfig(); + return createRequesterForSingleResponse(namespace, payloadClass, msbConfig.getDefaultResponseTimeout()); + } + /** + * {@inheritDoc} + */ + @Override + public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout) { RequestOptions requestOptions = new RequestOptions.Builder() .withWaitForResponses(1) - .withResponseTimeout(msbConfig.getDefaultResponseTimeout()) - .withAckTimeout(msbConfig.getDefaultAckTimeout()) + .withResponseTimeout(timeout) + .withAckTimeout(0) .build(); - return RequesterImpl.create(namespace, requestOptions, msbContext, toTypeReference(payloadClass)); } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 471df44a..7a8dafff 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -32,7 +32,6 @@ msbConfig { requestOptions { responseTimeout = 5000 - ackTimeout = 5000 } } diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java index 9a299cdf..45f1691e 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java @@ -29,10 +29,15 @@ public Requester createRequester(String namespace, RequestOptions request @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass) { + return createRequesterForSingleResponse(namespace, payloadClass, 100); + } + + @Override + public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout) { RequestOptions requestOptions = new RequestOptions.Builder() .withWaitForResponses(1) - .withResponseTimeout(100) - .withAckTimeout(100) + .withResponseTimeout(timeout) + .withAckTimeout(0) .build(); return createRequester(namespace, requestOptions, toTypeReference(payloadClass)); From e81faadda56e155ee14528067c94a01358b5e53a Mon Sep 17 00:00:00 2001 From: anha1 Date: Tue, 17 May 2016 15:33:37 +0300 Subject: [PATCH 121/226] multithreading messages handling code was extracted from AMQP module; it is possible to process custom groups of messages in a single threaded mode --- .../MultipleRequesterResponder.java | 67 +++----- .../tcdl/msb/acceptance/SimpleResponder.java | 3 +- .../MultipleRequesterResponderRunner.java | 1 + .../msb/adapters/amqp/AmqpAdapterFactory.java | 47 +---- .../AmqpMessageHandlerInvokeStrategy.java | 35 ---- .../msb/config/amqp/AmqpBrokerConfig.java | 24 +-- amqp/src/main/resources/amqp.conf | 3 - .../amqp/AmqpAdapterFactoryExecutorTest.java | 72 -------- .../adapters/amqp/AmqpAdapterFactoryTest.java | 31 +--- .../amqp/AmqpConsumerAdapterTest.java | 4 +- .../AmqpMessageHandlerInvokeStrategyTest.java | 47 ----- ...st.java => MessageProcessingTaskTest.java} | 11 +- .../msb/config/amqp/AmqpBrokerConfigTest.java | 68 -------- .../test/resources/broker_bounded_queue.conf | 7 - .../resources/broker_unbounded_queue.conf | 7 - .../io/github/tcdl/msb/ChannelManager.java | 27 +-- .../java/io/github/tcdl/msb/Consumer.java | 12 +- .../tcdl/msb/adapters/AdapterFactory.java | 7 +- .../tcdl/msb/api/MsbContextBuilder.java | 37 +++- .../io/github/tcdl/msb/config/MsbConfig.java | 21 ++- .../threading/ConsumerExecutorFactory.java | 11 ++ .../ConsumerExecutorFactoryImpl.java | 29 ++++ .../DirectMessageHandlerInvoker.java} | 13 +- .../ExecutorBasedMessageHandlerInvoker.java | 33 ++++ ...pedExecutorBasedMessageHandlerInvoker.java | 63 +++++++ .../msb/threading/MessageGroupStrategy.java | 23 +++ .../MessageHandlerInvoker.java} | 11 +- .../msb/threading/MessageProcessingTask.java | 22 ++- .../ThreadPoolMessageHandlerInvoker.java | 38 +++++ core/src/main/resources/reference.conf | 6 + .../msb/ChannelManagerConcurrentTest.java | 10 +- .../github/tcdl/msb/ChannelManagerTest.java | 12 +- .../java/io/github/tcdl/msb/ConsumerTest.java | 83 +++++---- .../adapterfactory/TestMsbAdapterFactory.java | 6 +- .../io/github/tcdl/msb/support/TestUtils.java | 25 ++- .../ConsumerExecutorFactoryTest.java | 61 +++++++ .../DirectMessageHandlerInvokerTest.java} | 12 +- ...xecutorBasedMessageHandlerInvokerTest.java | 161 ++++++++++++++++++ ...readPoolMessageHandlerInvokerImplTest.java | 81 +++++++++ doc/MSB.md | 20 ++- release-notes.html | 9 +- 41 files changed, 787 insertions(+), 473 deletions(-) delete mode 100644 amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java delete mode 100644 amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java delete mode 100644 amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategyTest.java rename amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/{AmqpMessageProcessingTaskTest.java => MessageProcessingTaskTest.java} (89%) delete mode 100644 amqp/src/test/resources/broker_bounded_queue.conf delete mode 100644 amqp/src/test/resources/broker_unbounded_queue.conf create mode 100644 core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactory.java create mode 100644 core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryImpl.java rename core/src/main/java/io/github/tcdl/msb/{impl/SimpleMessageHandlerInvokeStrategyImpl.java => threading/DirectMessageHandlerInvoker.java} (60%) create mode 100644 core/src/main/java/io/github/tcdl/msb/threading/ExecutorBasedMessageHandlerInvoker.java create mode 100644 core/src/main/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvoker.java create mode 100644 core/src/main/java/io/github/tcdl/msb/threading/MessageGroupStrategy.java rename core/src/main/java/io/github/tcdl/msb/{adapters/MessageHandlerInvokeStrategy.java => threading/MessageHandlerInvoker.java} (84%) rename amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java => core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java (76%) create mode 100644 core/src/main/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvoker.java create mode 100644 core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java rename core/src/test/java/io/github/tcdl/msb/{impl/SimpleMessageHandlerInvokeStrategyImplTest.java => threading/DirectMessageHandlerInvokerTest.java} (78%) create mode 100644 core/src/test/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvokerTest.java create mode 100644 core/src/test/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvokerImplTest.java diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java index ad8b0797..9fc799c0 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java @@ -2,11 +2,11 @@ import io.github.tcdl.msb.api.Requester; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; public class MultipleRequesterResponder { @@ -19,6 +19,9 @@ public class MultipleRequesterResponder { private String requesterNamespace1; private String requesterNamespace2; + private final AtomicInteger responseCounter = new AtomicInteger(); + private final List responseBodies = new CopyOnWriteArrayList<>(); + MultipleRequesterResponder(String responderNamespace, String requesterNamespace1, String requesterNamespace2) { this.responderNamespace = responderNamespace; this.requesterNamespace1 = requesterNamespace1; @@ -26,26 +29,13 @@ public class MultipleRequesterResponder { } public void runMultipleRequesterResponder() { - BasicThreadFactory threadFactory = new BasicThreadFactory.Builder() - .namingPattern("MultipleRequesterResponder-%d") - .build(); - - ExecutorService executor = Executors.newFixedThreadPool(2, threadFactory); - util.createResponderServer(responderNamespace, (request, responderContext) -> { System.out.print(">>> REQUEST: " + request); - - Future futureRequester1 = createAndRunRequester(executor, requesterNamespace1); - Future futureRequester2 = createAndRunRequester(executor, requesterNamespace2); - - Thread.sleep(500); - - String result1 = futureRequester1.get(); - String result2 = futureRequester2.get(); - - executor.shutdownNow(); - - responderContext.getResponder().send("response from MultipleRequesterResponder:" + (result1 + result2)); + Runnable onFinalResponse = () -> { + responderContext.getResponder().send("response from MultipleRequesterResponder:" + StringUtils.join(responseBodies)); + }; + createAndRunRequester(requesterNamespace1, onFinalResponse); + createAndRunRequester(requesterNamespace2, onFinalResponse); }, String.class) .listen(); } @@ -54,29 +44,18 @@ public void shutdown() { util.shutdown(); } - private Future createAndRunRequester(ExecutorService executor, String namespace) { + private void createAndRunRequester(String namespace, Runnable onFinalResponse) { Requester requester = util.createRequester(namespace, NUMBER_OF_RESPONSES, null, 5000, String.class); - Future future = executor.submit(new Callable() { - String result = null; - - @Override - public String call() throws Exception { - util.sendRequest(requester, "PING", NUMBER_OF_RESPONSES, response -> { - System.out.println(">>> RESPONSE body: " + response); - result = response; - synchronized (this) { - notify(); - } - - }); - - synchronized (this) { - wait(); + try { + util.sendRequest(requester, "PING", NUMBER_OF_RESPONSES, response -> { + System.out.println(">>> RESPONSE body: " + response); + responseBodies.add(response); + if(responseCounter.incrementAndGet() == 2) { + onFinalResponse.run(); } - - return result; - } - }); - return future; + }); + } catch (Exception ex) { + throw new RuntimeException(ex); + } } } diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/SimpleResponder.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/SimpleResponder.java index 5c76bad5..ad04351a 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/SimpleResponder.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/SimpleResponder.java @@ -15,8 +15,7 @@ public class SimpleResponder { public void runSimpleResponderExample() { helper.initDefault(); helper.createResponderServer(namespace, (request, responderContext) -> { - System.out.print(">>> REQUEST: " + request); - Thread.sleep(500); + System.out.println(">>> REQUEST: " + request); responderContext.getResponder().send(namespace + ":" + "SimpleResponder"); }, String.class) .listen(); diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponderRunner.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponderRunner.java index 712b5273..107ac49d 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponderRunner.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponderRunner.java @@ -29,6 +29,7 @@ public void runTest() throws Exception { responderExample1.runSimpleResponderExample(); responderExample2.runSimpleResponderExample(); multipleRequesterResponder.runMultipleRequesterResponder(); + Thread.sleep(500); requesterExample.runSimpleRequesterExample("test:simple-queue2", "test:simple-queue3"); assertTrue(requesterExample.isPassed()); diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java index 9f8300fd..e740ea64 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java @@ -7,14 +7,11 @@ import com.typesafe.config.ConfigFactory; import io.github.tcdl.msb.adapters.AdapterFactory; import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy; import io.github.tcdl.msb.adapters.ProducerAdapter; import io.github.tcdl.msb.api.exception.ChannelException; import io.github.tcdl.msb.api.exception.ConfigurationException; import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; -import io.github.tcdl.msb.support.Utils; -import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,11 +26,8 @@ public class AmqpAdapterFactory implements AdapterFactory { private static final Logger LOG = LoggerFactory.getLogger(AmqpAdapterFactory.class); - private static final int QUEUE_SIZE_UNLIMITED = -1; - - private AmqpBrokerConfig amqpBrokerConfig; - private AmqpConnectionManager connectionManager; - private ExecutorService consumerThreadPool; + private volatile AmqpBrokerConfig amqpBrokerConfig; + private volatile AmqpConnectionManager connectionManager; /** * @throws ChannelException if an error is encountered during connecting to broker @@ -45,7 +39,6 @@ public void init(MsbConfig msbConfig) { ConnectionFactory connectionFactory = createConnectionFactory(amqpBrokerConfig); Connection connection = createConnection(connectionFactory); connectionManager = createConnectionManager(connection); - consumerThreadPool = createConsumerThreadPool(amqpBrokerConfig); } protected AmqpBrokerConfig createAmqpBrokerConfig(MsbConfig msbConfig) { @@ -139,9 +132,12 @@ protected Connection createConnection(ConnectionFactory connectionFactory) { } @Override - public void shutdown() { - Utils.gracefulShutdown(consumerThreadPool, "consumer"); + public boolean isUseMsbThreadingModel() { + return true; + } + @Override + public void shutdown() { try { connectionManager.close(); } catch (IOException e) { @@ -149,31 +145,6 @@ public void shutdown() { } } - protected ExecutorService createConsumerThreadPool(AmqpBrokerConfig amqpBrokerConfig) { - BasicThreadFactory threadFactory = new BasicThreadFactory.Builder() - .namingPattern("amqp-consumer-thread-%d") - .build(); - int numberOfThreads = amqpBrokerConfig.getConsumerThreadPoolSize(); - int queueCapacity = amqpBrokerConfig.getConsumerThreadPoolQueueCapacity(); - - BlockingQueue queue; - if (queueCapacity == QUEUE_SIZE_UNLIMITED) { - queue = new LinkedBlockingQueue<>(); - } else { - queue = new ArrayBlockingQueue<>(queueCapacity); - } - - return new ThreadPoolExecutor(numberOfThreads, numberOfThreads, - 0L, TimeUnit.MILLISECONDS, - queue, - threadFactory); - } - - @Override - public MessageHandlerInvokeStrategy createMessageHandlerInvokeStrategy(String topic) { - return new AmqpMessageHandlerInvokeStrategy(consumerThreadPool); - } - AmqpBrokerConfig getAmqpBrokerConfig() { return amqpBrokerConfig; } @@ -182,8 +153,4 @@ AmqpConnectionManager getConnectionManager() { return connectionManager; } - ExecutorService getConsumerThreadPool() { - return consumerThreadPool; - } - } diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java deleted file mode 100644 index ed0cc827..00000000 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategy.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.tcdl.msb.adapters.amqp; - -import io.github.tcdl.msb.MessageHandler; -import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; -import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy; -import io.github.tcdl.msb.api.message.Message; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.ExecutorService; - -/** - * {@link MessageHandlerInvokeStrategy} implementation that preforms a an {@link MessageHandler} invocation - * in a separate thread. - */ -public class AmqpMessageHandlerInvokeStrategy implements MessageHandlerInvokeStrategy { - - private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageHandlerInvokeStrategy.class); - - private final ExecutorService consumerThreadPool; - - /** - * @param consumerThreadPool thread pool to be used for {@link MessageHandler} invocations. - */ - public AmqpMessageHandlerInvokeStrategy(ExecutorService consumerThreadPool) { - this.consumerThreadPool = consumerThreadPool; - } - - @Override - public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) { - consumerThreadPool.submit(new AmqpMessageProcessingTask(messageHandler, message, acknowledgeHandler)); - LOG.debug("[correlation id: {}] Message has been put in the processing queue.", - message.getCorrelationId()); - } -} diff --git a/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java b/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java index fa9720be..3bd88fcb 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java +++ b/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java @@ -21,15 +21,13 @@ public class AmqpBrokerConfig { private Optional groupId; private final boolean durable; - private final int consumerThreadPoolSize; - private final int consumerThreadPoolQueueCapacity; private final int heartbeatIntervalSec; private final long networkRecoveryIntervalMs; private final int prefetchCount; public AmqpBrokerConfig(Charset charset, String host, int port, Optional username, Optional password, Optional virtualHost, boolean useSSL, - Optional groupId, boolean durable, int consumerThreadPoolSize, int consumerThreadPoolQueueCapacity, + Optional groupId, boolean durable, int heartbeatIntervalSec, long networkRecoveryIntervalMs, int prefetchCount) { this.charset = charset; this.port = port; @@ -40,8 +38,6 @@ public AmqpBrokerConfig(Charset charset, String host, int port, this.useSSL = useSSL; this.groupId = groupId; this.durable = durable; - this.consumerThreadPoolSize = consumerThreadPoolSize; - this.consumerThreadPoolQueueCapacity = consumerThreadPoolQueueCapacity; this.heartbeatIntervalSec = heartbeatIntervalSec; this.networkRecoveryIntervalMs = networkRecoveryIntervalMs; this.prefetchCount = prefetchCount; @@ -57,8 +53,6 @@ public static class AmqpBrokerConfigBuilder { private boolean useSSL; private Optional groupId; private boolean durable; - private int consumerThreadPoolSize; - private int consumerThreadPoolQueueCapacity; private int heartbeatIntervalSec; private long networkRecoveryIntervalMs; private int prefetchCount; @@ -86,8 +80,6 @@ public AmqpBrokerConfigBuilder withConfig(Config config) { this.groupId = ConfigurationUtil.getOptionalString(config, "groupId"); this.durable = ConfigurationUtil.getBoolean(config, "durable"); - this.consumerThreadPoolSize = ConfigurationUtil.getInt(config, "consumerThreadPoolSize"); - this.consumerThreadPoolQueueCapacity = ConfigurationUtil.getInt(config, "consumerThreadPoolQueueCapacity"); this.heartbeatIntervalSec = ConfigurationUtil.getInt(config, "heartbeatIntervalSec"); this.networkRecoveryIntervalMs = ConfigurationUtil.getLong(config, "networkRecoveryIntervalMs"); this.prefetchCount = ConfigurationUtil.getInt(config, "prefetchCount"); @@ -99,7 +91,7 @@ public AmqpBrokerConfigBuilder withConfig(Config config) { */ public AmqpBrokerConfig build() { return new AmqpBrokerConfig(charset, host, port, username, password, virtualHost, useSSL, - groupId, durable, consumerThreadPoolSize, consumerThreadPoolQueueCapacity, + groupId, durable, heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); } } @@ -144,14 +136,6 @@ public void setGroupId(Optional groupId) { this.groupId = groupId; } - public int getConsumerThreadPoolSize() { - return consumerThreadPoolSize; - } - - public int getConsumerThreadPoolQueueCapacity() { - return consumerThreadPoolQueueCapacity; - } - public int getHeartbeatIntervalSec() { return heartbeatIntervalSec; } @@ -167,9 +151,9 @@ public int getPrefetchCount() { @Override public String toString() { return String.format("AmqpBrokerConfig [charset=%s, host=%s, port=%d, username=%s, password=xxx, virtualHost=%s, useSSL=%s, groupId=%s, durable=%s, " - + "consumerThreadPoolSize=%s, consumerThreadPoolQueueCapacity=%s, heartbeatIntervalSec=%s, " + + "heartbeatIntervalSec=%s, " + "networkRecoveryIntervalMs=%s, prefetchCount=%s]", - charset, host, port, username, virtualHost, useSSL, groupId, durable, consumerThreadPoolSize, consumerThreadPoolQueueCapacity, + charset, host, port, username, virtualHost, useSSL, groupId, durable, heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); } diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf index 0a24bb50..af6ad658 100644 --- a/amqp/src/main/resources/amqp.conf +++ b/amqp/src/main/resources/amqp.conf @@ -15,9 +15,6 @@ config.amqp = { #groupId = "msb-java" durable = false - consumerThreadPoolSize = 5 - # -1 means unlimited - consumerThreadPoolQueueCapacity = -1 # Interval of the heartbeats that are used to detect broken connections. Zero for none. See for more details: https://www.rabbitmq.com/heartbeats.html heartbeatIntervalSec = 30 diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java deleted file mode 100644 index f1b05603..00000000 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryExecutorTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.github.tcdl.msb.adapters.amqp; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.*; - -import io.github.tcdl.msb.config.MsbConfig; - -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import org.junit.Test; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Recoverable; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class AmqpAdapterFactoryExecutorTest { - private static final Config CONFIG = ConfigFactory.load("reference.conf"); - - private static final Config CONFIG_BOUNDED = CONFIG.withFallback(ConfigFactory.load("broker_bounded_queue.conf")); - private static final Config CONFIG_UNBOUNDED = CONFIG.withFallback(ConfigFactory.load( "broker_unbounded_queue.conf")); - - private static class MockAdapterFactory extends AmqpAdapterFactory { - @Override - protected Connection createConnection(ConnectionFactory connectionFactory) { - return mock(Connection.class, withSettings().extraInterfaces(Recoverable.class)); - } - } - - @Test - public void testCreateConsumerThreadPoolBoundedQueue() { - MsbConfig msbConfigurations = new MsbConfig(CONFIG_BOUNDED); - - AmqpAdapterFactory adapterFactory = new MockAdapterFactory(); - adapterFactory.init(msbConfigurations); - ExecutorService consumerThreadPool = adapterFactory.createConsumerThreadPool(adapterFactory.getAmqpBrokerConfig()); - - assertNotNull(consumerThreadPool); - assertTrue(consumerThreadPool instanceof ThreadPoolExecutor); - ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) consumerThreadPool; - - BlockingQueue queue = threadPoolExecutor.getQueue(); - assertNotNull(queue); - assertTrue(queue instanceof ArrayBlockingQueue); - assertEquals(20, ((ArrayBlockingQueue) queue).remainingCapacity()); - } - - @Test - public void testCreateConsumerThreadPoolUnboundedQueue() { - MsbConfig msbConfigurations = new MsbConfig(CONFIG_UNBOUNDED); - - AmqpAdapterFactory adapterFactory = new MockAdapterFactory(); - adapterFactory.init(msbConfigurations); - ExecutorService consumerThreadPool = adapterFactory.createConsumerThreadPool(adapterFactory.getAmqpBrokerConfig()); - - assertNotNull(consumerThreadPool); - assertTrue(consumerThreadPool instanceof ThreadPoolExecutor); - ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) consumerThreadPool; - - BlockingQueue queue = threadPoolExecutor.getQueue(); - assertNotNull(queue); - assertTrue(queue instanceof LinkedBlockingQueue); - } -} \ No newline at end of file diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java index c1655802..aa335fd5 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java @@ -4,12 +4,10 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; + import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy; import io.github.tcdl.msb.adapters.ProducerAdapter; import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig; @@ -17,8 +15,6 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; @@ -45,8 +41,6 @@ public class AmqpAdapterFactoryTest { final boolean useSSL = false; final String groupId = "msb-java"; final boolean durable = false; - final int consumerThreadPoolSize = 5; - final int consumerThreadPoolQueueCapacity = 20; final int heartbeatIntervalSec = 1; final long networkRecoveryIntervalMs = 5000; final int prefetchCount = 1; @@ -60,9 +54,6 @@ public class AmqpAdapterFactoryTest { @Mock Connection mockConnection; - @Mock - ExecutorService mockConsumerThreadPool; - AmqpBrokerConfig amqpConfig; AmqpAdapterFactory amqpAdapterFactory; MsbConfig msbConfigurations; @@ -72,16 +63,8 @@ public void setUp() { msbConfigurations = new MsbConfig(CONFIG); - //Define conditions for ExecutorService termination - try { - when(mockConsumerThreadPool.awaitTermination(anyInt(), any(TimeUnit.class))).thenReturn(true); - } catch (InterruptedException e) { - fail("Can't create mockConsumerThreadPool"); - } - amqpConfig = new AmqpBrokerConfig(charset, host, port, Optional.of(username), Optional.of(password), Optional.of(virtualHost), useSSL, Optional.of(groupId), durable, - consumerThreadPoolSize, consumerThreadPoolQueueCapacity, heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount); amqpAdapterFactory = new AmqpAdapterFactory() { @@ -115,10 +98,6 @@ protected Connection createConnection(ConnectionFactory connectionFactory) { return mockConnection; } - @Override - protected ExecutorService createConsumerThreadPool(AmqpBrokerConfig amqpBrokerConfig) { - return mockConsumerThreadPool; - } }; } @@ -137,7 +116,6 @@ public void testInit() { amqpAdapterFactory.init(msbConfigurations); assertEquals(amqpAdapterFactory.getAmqpBrokerConfig(), amqpConfig); assertEquals(amqpAdapterFactory.getConnectionManager(), mockConnectionManager); - assertEquals(amqpAdapterFactory.getConsumerThreadPool(), mockConsumerThreadPool); } @Test @@ -146,17 +124,10 @@ public void testInitGroupIdWithServiceName() { assertEquals(amqpBrokerConfig.getGroupId().get(), msbConfigurations.getServiceDetails().getName()); } - @Test - public void testCreateMessageHandlerInvokeAdapter() { - MessageHandlerInvokeStrategy adapter = amqpAdapterFactory.createMessageHandlerInvokeStrategy("any"); - assertTrue(adapter instanceof AmqpMessageHandlerInvokeStrategy); - } - @Test public void testShutdown() { amqpAdapterFactory.init(msbConfigurations); amqpAdapterFactory.shutdown(); - verify(mockConsumerThreadPool).shutdown(); try { verify(mockConnectionManager).close(); } catch (IOException e) { diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java index 0b5e17f2..e3b4cd0e 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java @@ -196,14 +196,14 @@ public void testIsDurableTrueIfNotResponseTopicAndDurableConfig() throws IOExcep private AmqpConsumerAdapter createAdapterWithNonDurableConf(String topic, String groupId, boolean isResponseTopic) { boolean isDurableConf = false; AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(), - false, Optional.of(groupId), isDurableConf, 5, 20, 1, 5000, 1); + false, Optional.of(groupId), isDurableConf, 1, 5000, 1); return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic); } private AmqpConsumerAdapter createAdapterWithDurableConf(String topic, String groupId, boolean isResponseTopic) { boolean isDurableConf = true; AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(), - false, Optional.of(groupId), isDurableConf, 5, 20, 1, 5000, 1); + false, Optional.of(groupId), isDurableConf, 1, 5000, 1); return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic); } } \ No newline at end of file diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategyTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategyTest.java deleted file mode 100644 index f9de0c8d..00000000 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageHandlerInvokeStrategyTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.tcdl.msb.adapters.amqp; - -import io.github.tcdl.msb.MessageHandler; -import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.support.TestUtils; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import java.util.concurrent.ExecutorService; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class AmqpMessageHandlerInvokeStrategyTest { - @Mock - ExecutorService mockExecutor; - - @Mock - AcknowledgementHandlerInternal acknowledgeHandler; - - @Mock - MessageHandler messageHandler; - - Message message = TestUtils.createMsbRequestMessage("any","any"); - - @InjectMocks - AmqpMessageHandlerInvokeStrategy adapter; - - @Test - public void testMessageHandling() { - adapter.execute(messageHandler, message, acknowledgeHandler); - verify(messageHandler, never()).handleMessage(any(), any()); - ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AmqpMessageProcessingTask.class); - verify(mockExecutor, times(1)).submit(taskCaptor.capture()); - AmqpMessageProcessingTask task = taskCaptor.getValue(); - - assertEquals(message, task.message); - assertEquals(messageHandler, task.messageHandler); - assertEquals(acknowledgeHandler, task.ackHandler); - } -} diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/MessageProcessingTaskTest.java similarity index 89% rename from amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java rename to amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/MessageProcessingTaskTest.java index 5f63f865..0865ae65 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTaskTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/MessageProcessingTaskTest.java @@ -14,6 +14,7 @@ import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl; import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.threading.MessageProcessingTask; import org.junit.Before; import org.junit.Test; @@ -24,7 +25,7 @@ import org.slf4j.MDC; @RunWith(MockitoJUnitRunner.class) -public class AmqpMessageProcessingTaskTest { +public class MessageProcessingTaskTest { private final String MDC_KEY = "key"; private final String MDC_VALUE = "any"; @@ -38,12 +39,12 @@ public class AmqpMessageProcessingTaskTest { @Mock private AcknowledgementHandlerImpl mockAcknowledgementHandler; - private AmqpMessageProcessingTask task; + private MessageProcessingTask task; @Before public void setUp() { message = TestUtils.createSimpleRequestMessage("any"); - task = new AmqpMessageProcessingTask(mockMessageHandler, message, mockAcknowledgementHandler); + task = new MessageProcessingTask(mockMessageHandler, message, mockAcknowledgementHandler); } @Test @@ -88,8 +89,8 @@ private boolean isMdcPresentInThread() throws Exception{ MessageHandler mdcMessageHandler = (message, acknowledgeHandler) -> { isMdcPresentInTaskRun.complete(isMdcPresent()); }; - AmqpMessageProcessingTask mdcTask = - new AmqpMessageProcessingTask(mdcMessageHandler, message, mockAcknowledgementHandler); + MessageProcessingTask mdcTask = + new MessageProcessingTask(mdcMessageHandler, message, mockAcknowledgementHandler); ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); singleThreadExecutor.execute(mdcTask); singleThreadExecutor.execute(() -> isMdcPresentInOtherRun.complete(isMdcPresent())); diff --git a/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java b/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java index dccb3865..7b60e634 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java @@ -24,8 +24,6 @@ public class AmqpBrokerConfigTest { final boolean useSSL = false; final String groupId = "msb-java"; final boolean durable = false; - final int consumerThreadPoolSize = 5; - final int consumerThreadPoolQueueCapacity = 20; final int heartbeatIntervalSec = 1; final long networkRecoveryIntervalMs = 5000; final int prefetchCount = 1; @@ -42,8 +40,6 @@ public void testBuildAmqpBrokerConfig() { + " useSSL = \"" + useSSL + "\"\n" + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -58,8 +54,6 @@ public void testBuildAmqpBrokerConfig() { assertEquals(brokerConfig.getPort(), port); assertEquals(brokerConfig.getGroupId().get(), groupId); assertEquals(brokerConfig.isDurable(), durable); - assertEquals(brokerConfig.getConsumerThreadPoolSize(), consumerThreadPoolSize); - assertEquals(brokerConfig.getConsumerThreadPoolQueueCapacity(), consumerThreadPoolQueueCapacity); assertEquals(brokerConfig.getUsername().get(), username); assertEquals(brokerConfig.getPassword().get(), password); @@ -81,8 +75,6 @@ public void testOptionalConfigurationOptions() { + " port = \"" + port + "\"\n" + " useSSL = \"" + useSSL + "\"\n" + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -111,8 +103,6 @@ public void testHostConfigurationOption() { + " useSSL = \"" + useSSL + "\"\n" + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -132,8 +122,6 @@ public void testPortConfigurationOption() { + " useSSL = \"" + useSSL + "\"\n" + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -153,8 +141,6 @@ public void testDurableConfigurationOption() { + " virtualHost = \"" + virtualHost + "\"\n" + " useSSL = \"" + useSSL + "\"\n" + " groupId = \"" + groupId + "\"\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -163,48 +149,6 @@ public void testDurableConfigurationOption() { testMandatoryConfigurationOption(configStr, "durable"); } - @Test - public void testConsumerThreadPoolSizeConfigurationOption() { - String configStr = "config.amqp {" - + " charsetName = \"" + charsetName + "\"\n" - + " host = \"" + host + "\"\n" - + " port = \"" + port + "\"\n" - + " username = \"" + username + "\"\n" - + " password = \"" + password + "\"\n" - + " virtualHost = \"" + virtualHost + "\"\n" - + " useSSL = \"" + useSSL + "\"\n" - + " groupId = \"" + groupId + "\"\n" - + " durable = " + durable + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" - + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" - + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" - + " prefetchCount = " + prefetchCount + "\n" - + "}"; - - testMandatoryConfigurationOption(configStr, "consumerThreadPoolSize"); - } - - @Test - public void testConsumerThreadPoolQueueCapacityConfigurationOption() { - String configStr = "config.amqp {" - + " charsetName = \"" + charsetName + "\"\n" - + " host = \"" + host + "\"\n" - + " port = \"" + port + "\"\n" - + " username = \"" + username + "\"\n" - + " password = \"" + password + "\"\n" - + " virtualHost = \"" + virtualHost + "\"\n" - + " useSSL = \"" + useSSL + "\"\n" - + " groupId = \"" + groupId + "\"\n" - + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" - + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" - + " prefetchCount = " + prefetchCount + "\n" - + "}"; - - testMandatoryConfigurationOption(configStr, "consumerThreadPoolQueueCapacity"); - } - @Test public void testCharsetConfigurationOption() { String configStr = "config.amqp {" @@ -216,8 +160,6 @@ public void testCharsetConfigurationOption() { + " useSSL = \"" + useSSL + "\"\n" + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -240,8 +182,6 @@ public void testInvalidCharset() { + " useSSL = \"" + useSSL + "\"\n" + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -262,8 +202,6 @@ public void testUseSSLConfigurationOption() { + " virtualHost = \"" + virtualHost + "\"\n" + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" @@ -284,8 +222,6 @@ public void testHeartbeatIntervalOption() { + " useSSL = \"" + useSSL + "\"\n" + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + " prefetchCount = " + prefetchCount + "\n" + "}"; @@ -305,8 +241,6 @@ public void testNetworkRecoveryIntervalOption() { + " useSSL = \"" + useSSL + "\"\n" + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " prefetchCount = " + prefetchCount + "\n" + "}"; @@ -326,8 +260,6 @@ public void testPrefetchCountOption() { + " useSSL = \"" + useSSL + "\"\n" + " groupId = \"" + groupId + "\"\n" + " durable = " + durable + "\n" - + " consumerThreadPoolSize = " + consumerThreadPoolSize + "\n" - + " consumerThreadPoolQueueCapacity = " + consumerThreadPoolQueueCapacity + "\n" + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n" + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n" + "}"; diff --git a/amqp/src/test/resources/broker_bounded_queue.conf b/amqp/src/test/resources/broker_bounded_queue.conf deleted file mode 100644 index 66770e46..00000000 --- a/amqp/src/test/resources/broker_bounded_queue.conf +++ /dev/null @@ -1,7 +0,0 @@ -msbConfig { - brokerConfig = { - charsetName = "UTF-8" - consumerThreadPoolSize = 5 - consumerThreadPoolQueueCapacity = 20 - } -} \ No newline at end of file diff --git a/amqp/src/test/resources/broker_unbounded_queue.conf b/amqp/src/test/resources/broker_unbounded_queue.conf deleted file mode 100644 index b7febdbb..00000000 --- a/amqp/src/test/resources/broker_unbounded_queue.conf +++ /dev/null @@ -1,7 +0,0 @@ -msbConfig { - brokerConfig = { - charsetName = "UTF-8" - consumerThreadPoolSize = 5 - consumerThreadPoolQueueCapacity = -1 - } -} \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java index 9d1c1ded..610e4d70 100644 --- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java +++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java @@ -14,6 +14,7 @@ import io.github.tcdl.msb.impl.SimpleMessageHandlerResolverImpl; import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; import io.github.tcdl.msb.monitor.agent.NoopChannelMonitorAgent; +import io.github.tcdl.msb.threading.MessageHandlerInvoker; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.Utils; import org.apache.commons.lang3.Validate; @@ -28,22 +29,24 @@ public class ChannelManager { private static final Logger LOG = LoggerFactory.getLogger(ChannelManager.class); private static final String RESPONDER_LOGGING_NAME = "Responder server"; - private MsbConfig msbConfig; - private Clock clock; - private JsonValidator validator; - private ObjectMapper messageMapper; - private AdapterFactory adapterFactory; + private final MsbConfig msbConfig; + private final Clock clock; + private final JsonValidator validator; + private final ObjectMapper messageMapper; + private final AdapterFactory adapterFactory; + private final MessageHandlerInvoker messageHandlerInvoker; private ChannelMonitorAgent channelMonitorAgent; - private Map producersByTopic; - private Map consumersByTopic; + private final Map producersByTopic; + private final Map consumersByTopic; - public ChannelManager(MsbConfig msbConfig, Clock clock, JsonValidator validator, ObjectMapper messageMapper) { + public ChannelManager(MsbConfig msbConfig, Clock clock, JsonValidator validator, ObjectMapper messageMapper, AdapterFactory adapterFactory, MessageHandlerInvoker messageHandlerInvoker) { this.msbConfig = msbConfig; this.clock = clock; this.validator = validator; this.messageMapper = messageMapper; - this.adapterFactory = new AdapterFactoryLoader(msbConfig).getAdapterFactory(); + this.adapterFactory = adapterFactory; + this.messageHandlerInvoker = messageHandlerInvoker; this.producersByTopic = new ConcurrentHashMap<>(); this.consumersByTopic = new ConcurrentHashMap<>(); @@ -129,13 +132,14 @@ private Consumer createConsumer(String topic, boolean isResponseTopic, MessageHa Utils.validateTopic(topic); ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic, isResponseTopic ); - MessageHandlerInvokeStrategy invokeAdapter = getAdapterFactory().createMessageHandlerInvokeStrategy(topic); - return new Consumer(adapter, invokeAdapter, topic, messageHandlerResolver, msbConfig, clock, channelMonitorAgent, validator, messageMapper); + + return new Consumer(adapter, messageHandlerInvoker, topic, messageHandlerResolver, msbConfig, clock, channelMonitorAgent, validator, messageMapper); } public void shutdown() { LOG.info("Shutting down..."); adapterFactory.shutdown(); + messageHandlerInvoker.shutdown(); LOG.info("Shutdown complete"); } @@ -143,6 +147,7 @@ private AdapterFactory getAdapterFactory() { return this.adapterFactory; } + public void setChannelMonitorAgent(ChannelMonitorAgent channelMonitorAgent) { this.channelMonitorAgent = channelMonitorAgent; } diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index 3b1bbddf..92981535 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -2,7 +2,7 @@ import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; -import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy; +import io.github.tcdl.msb.threading.MessageHandlerInvoker; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.MetaMessage; import io.github.tcdl.msb.collector.ConsumedMessagesAwareMessageHandler; @@ -32,7 +32,7 @@ public class Consumer { private static final Logger LOG = LoggerFactory.getLogger(Consumer.class); private final ConsumerAdapter rawAdapter; - private final MessageHandlerInvokeStrategy messageHandlerInvokeStrategy; + private final MessageHandlerInvoker messageHandlerInvoker; private final String topic; private final MsbConfig msbConfig; private final ChannelMonitorAgent channelMonitorAgent; @@ -53,13 +53,13 @@ public class Consumer { * @param validator validates incoming messages * @param messageMapper message deserializer */ - public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvokeStrategy messageHandlerInvokeStrategy, + public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvoker messageHandlerInvoker, String topic, MessageHandlerResolver messageHandlerResolver, MsbConfig msbConfig, Clock clock, ChannelMonitorAgent channelMonitorAgent, JsonValidator validator, ObjectMapper messageMapper) { LOG.debug("Creating consumer for topic: {}", topic); Validate.notNull(rawAdapter, "the 'rawAdapter' must not be null"); - Validate.notNull(messageHandlerInvokeStrategy, "the 'messageHandlerInvokeStrategy' must not be null"); + Validate.notNull(messageHandlerInvoker, "the 'messageHandlerInvokeStrategy' must not be null"); Validate.notNull(topic, "the 'topic' must not be null"); Validate.notNull(messageHandlerResolver, "the 'messageHandlerResolver' must not be null"); Validate.notNull(msbConfig, "the 'msbConfig' must not be null"); @@ -69,7 +69,7 @@ public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvokeStrategy message Validate.notNull(messageMapper, "the 'messageMapper' must not be null"); this.rawAdapter = rawAdapter; - this.messageHandlerInvokeStrategy = messageHandlerInvokeStrategy; + this.messageHandlerInvoker = messageHandlerInvoker; this.topic = topic; this.messageHandlerResolver = messageHandlerResolver; this.msbConfig = msbConfig; @@ -133,7 +133,7 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern consumedMessagesAwareMessageHandler = ((ConsumedMessagesAwareMessageHandler) messageHandler); consumedMessagesAwareMessageHandler.notifyMessageConsumed(); } - messageHandlerInvokeStrategy.execute(messageHandler, message, acknowledgeHandler); + messageHandlerInvoker.execute(messageHandler, message, acknowledgeHandler); } else { LOG.warn("{} Cant't resolve message handler for a message: {}", loggingTag, jsonMessage); acknowledgeHandler.autoReject(); diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java index eac6f91a..2100a413 100644 --- a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java +++ b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java @@ -34,11 +34,10 @@ public interface AdapterFactory { ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic); /** - * Create {@link MessageHandlerInvokeStrategy} instance. - * @param topic topic name - * @return {@link MessageHandlerInvokeStrategy} instance associated with a topic. + * @return true if custom MSB threading model should be used. + * @return false if {@link io.github.tcdl.msb.MessageHandler} should be invoked directly. */ - MessageHandlerInvokeStrategy createMessageHandlerInvokeStrategy(String topic); + boolean isUseMsbThreadingModel(); /** * Closes all resources used by amqp producers and consumers. Should be called for graceful shutdown. diff --git a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java index b646339e..7f02f521 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java +++ b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java @@ -8,6 +8,8 @@ import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import io.github.tcdl.msb.ChannelManager; +import io.github.tcdl.msb.adapters.AdapterFactory; +import io.github.tcdl.msb.adapters.AdapterFactoryLoader; import io.github.tcdl.msb.api.exception.MsbException; import io.github.tcdl.msb.callback.MutableCallbackHandler; import io.github.tcdl.msb.collector.CollectorManagerFactory; @@ -18,6 +20,7 @@ import io.github.tcdl.msb.message.MessageFactory; import io.github.tcdl.msb.monitor.agent.DefaultChannelMonitorAgent; import io.github.tcdl.msb.support.JsonValidator; +import io.github.tcdl.msb.threading.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +38,7 @@ public class MsbContextBuilder { private boolean enableShutdownHook; private boolean enableChannelMonitorAgent; private ObjectMapper payloadMapper = createMessageEnvelopeMapper(); + private MessageGroupStrategy messageGroupStrategy; public MsbContextBuilder() { super(); @@ -50,6 +54,18 @@ public MsbContextBuilder withConfig(Config config) { return this; } + /** + * Provide a custom {@link MessageGroupStrategy} instance in order to process messages with the same groupId + * in a single-threaded mode. + * @param messageGroupStrategy + * @return + */ + + public MsbContextBuilder withMessageGroupStrategy(MessageGroupStrategy messageGroupStrategy) { + this.messageGroupStrategy = messageGroupStrategy; + return this; + } + /** * Specifies if to shutdown current context during JVM exit. * @param enableShutdownHook if set to true will shutdown context regardless of @@ -100,7 +116,9 @@ public MsbContext build() { MsbConfig msbConfig = new MsbConfig(config); ObjectMapper messageEnvelopeMapper = createMessageEnvelopeMapper(); - ChannelManager channelManager = new ChannelManager(msbConfig, clock, validator, messageEnvelopeMapper); + AdapterFactory adapterFactory = new AdapterFactoryLoader(msbConfig).getAdapterFactory(); + MessageHandlerInvoker messageHandlerInvoker = createMessageHandlerInvoker(adapterFactory, msbConfig); + ChannelManager channelManager = new ChannelManager(msbConfig, clock, validator, messageEnvelopeMapper, adapterFactory, messageHandlerInvoker); MessageFactory messageFactory = new MessageFactory(msbConfig.getServiceDetails(), clock, payloadMapper); TimeoutManager timeoutManager = new TimeoutManager(msbConfig.getTimerThreadPoolSize()); CollectorManagerFactory collectorManagerFactory = new CollectorManagerFactory(channelManager); @@ -131,6 +149,23 @@ public void run() { return msbContext; } + private MessageHandlerInvoker createMessageHandlerInvoker(AdapterFactory adapterFactory, MsbConfig msbConfig) { + ConsumerExecutorFactory consumerExecutorFactory = new ConsumerExecutorFactoryImpl(); + + if (adapterFactory.isUseMsbThreadingModel()) { + if(messageGroupStrategy == null) { + return new ThreadPoolMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(), + consumerExecutorFactory); + } else { + return new GroupedExecutorBasedMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(), + consumerExecutorFactory, + messageGroupStrategy); + } + } else { + return new DirectMessageHandlerInvoker(); + } + } + /** * @return creates an instance of "default" object mapper that is used to parse message envelope (without payload) */ diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java index bc2a7401..cd0d8929 100644 --- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java +++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java @@ -39,6 +39,10 @@ public class MsbConfig { private final String mdcLoggingSplitTagsBy; + private final int consumerThreadPoolSize; + + private final int consumerThreadPoolQueueCapacity; + public MsbConfig(Config loadedConfig) { Config config = loadedConfig.getConfig("msbConfig"); @@ -46,10 +50,14 @@ public MsbConfig(Config loadedConfig) { this.serviceDetails = new ServiceDetails.Builder(serviceDetailsConfig).build(); this.schema = readJsonSchema(); this.brokerAdapterFactoryClass = getBrokerAdapterFactory(config); + this.brokerConfig = config.hasPath("brokerConfig") ? config.getConfig("brokerConfig") : ConfigFactory.empty(); this.timerThreadPoolSize = getInt(config, "timerThreadPoolSize"); this.validateMessage = getBoolean(config, "validateMessage"); + this.consumerThreadPoolSize = config.getInt("threadingConfig.consumerThreadPoolSize"); + this.consumerThreadPoolQueueCapacity = config.getInt("threadingConfig.consumerThreadPoolQueueCapacity"); + Config mdcLogging = config.getConfig("mdcLogging"); Config mdcLoggingMessageKeys= mdcLogging.getConfig("messageKeys"); @@ -114,10 +122,9 @@ public String getMdcLoggingSplitTagsBy() { } @Override public String toString() { - //please keep custom "brokerConfig" data + //please keep custom "brokerConfig" when using auto-generation of this method return "MsbConfig{" + "brokerAdapterFactoryClass='" + brokerAdapterFactoryClass + '\'' + - ", brokerConfig=" + brokerConfig + ", serviceDetails=" + serviceDetails + ", schema='" + schema + '\'' + ", validateMessage=" + validateMessage + @@ -126,7 +133,17 @@ public String getMdcLoggingSplitTagsBy() { ", mdcLoggingKeyMessageTags='" + mdcLoggingKeyMessageTags + '\'' + ", mdcLoggingKeyCorrelationId='" + mdcLoggingKeyCorrelationId + '\'' + ", mdcLoggingSplitTagsBy='" + mdcLoggingSplitTagsBy + '\'' + + ", consumerThreadPoolSize=" + consumerThreadPoolSize + + ", consumerThreadPoolQueueCapacity=" + consumerThreadPoolQueueCapacity + ", brokerConfig='" + brokerConfig.root().render() + '\'' + '}'; } + + public int getConsumerThreadPoolSize() { + return consumerThreadPoolSize; + } + + public int getConsumerThreadPoolQueueCapacity() { + return consumerThreadPoolQueueCapacity; + } } diff --git a/core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactory.java b/core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactory.java new file mode 100644 index 00000000..3c85cd7d --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactory.java @@ -0,0 +1,11 @@ +package io.github.tcdl.msb.threading; + +import java.util.concurrent.ExecutorService; + +/** + * Implementations define a way to create {@link ExecutorService} instances + * used to invoke {@link io.github.tcdl.msb.MessageHandler}. + */ +public interface ConsumerExecutorFactory { + ExecutorService createConsumerThreadPool(int numberOfThreads, int queueCapacity); +} diff --git a/core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryImpl.java new file mode 100644 index 00000000..91da84e5 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryImpl.java @@ -0,0 +1,29 @@ +package io.github.tcdl.msb.threading; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; + +import java.util.concurrent.*; + +public class ConsumerExecutorFactoryImpl implements ConsumerExecutorFactory { + + protected static final int QUEUE_SIZE_UNLIMITED = -1; + + @Override + public ExecutorService createConsumerThreadPool(int numberOfThreads, int queueCapacity) { + BasicThreadFactory threadFactory = new BasicThreadFactory.Builder() + .namingPattern("msb-consumer-thread-%d") + .build(); + + BlockingQueue queue; + if (queueCapacity == QUEUE_SIZE_UNLIMITED) { + queue = new LinkedBlockingQueue<>(); + } else { + queue = new ArrayBlockingQueue<>(queueCapacity); + } + + return new ThreadPoolExecutor(numberOfThreads, numberOfThreads, + 0L, TimeUnit.MILLISECONDS, + queue, + threadFactory); + } +} diff --git a/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImpl.java b/core/src/main/java/io/github/tcdl/msb/threading/DirectMessageHandlerInvoker.java similarity index 60% rename from core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImpl.java rename to core/src/main/java/io/github/tcdl/msb/threading/DirectMessageHandlerInvoker.java index ca5887f2..bc11745b 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/threading/DirectMessageHandlerInvoker.java @@ -1,18 +1,23 @@ -package io.github.tcdl.msb.impl; +package io.github.tcdl.msb.threading; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; -import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy; import io.github.tcdl.msb.api.message.Message; /** - * Trivial {@link MessageHandlerInvokeStrategy} implementation that preforms a direct {@link MessageHandler} invocation + * Trivial {@link MessageHandlerInvoker} implementation that preforms a direct {@link MessageHandler} invocation * to process a {@link Message} received. */ -public class SimpleMessageHandlerInvokeStrategyImpl implements MessageHandlerInvokeStrategy { +public class DirectMessageHandlerInvoker implements MessageHandlerInvoker { + @Override public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) { messageHandler.handleMessage(message, acknowledgeHandler); acknowledgeHandler.autoConfirm(); } + + @Override + public void shutdown() { + + } } diff --git a/core/src/main/java/io/github/tcdl/msb/threading/ExecutorBasedMessageHandlerInvoker.java b/core/src/main/java/io/github/tcdl/msb/threading/ExecutorBasedMessageHandlerInvoker.java new file mode 100644 index 00000000..26775c19 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/threading/ExecutorBasedMessageHandlerInvoker.java @@ -0,0 +1,33 @@ +package io.github.tcdl.msb.threading; + +import io.github.tcdl.msb.MessageHandler; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; +import io.github.tcdl.msb.api.message.Message; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for {@link MessageHandlerInvoker} implementations that rely on a custom + * threading model. + */ +public abstract class ExecutorBasedMessageHandlerInvoker implements MessageHandlerInvoker { + + private static final Logger LOG = LoggerFactory.getLogger(ExecutorBasedMessageHandlerInvoker.class); + + protected final ConsumerExecutorFactory consumerExecutorFactory; + + public ExecutorBasedMessageHandlerInvoker(ConsumerExecutorFactory consumerExecutorFactory) { + this.consumerExecutorFactory = consumerExecutorFactory; + } + + @Override + public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) { + MessageProcessingTask task = new MessageProcessingTask(messageHandler, message, acknowledgeHandler); + doSubmitTask(task, message); + LOG.debug("[correlation id: {}] Message has been put in the processing queue.", + message.getCorrelationId()); + } + + protected abstract void doSubmitTask(MessageProcessingTask task, Message message); + +} diff --git a/core/src/main/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvoker.java b/core/src/main/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvoker.java new file mode 100644 index 00000000..a7e90501 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvoker.java @@ -0,0 +1,63 @@ +package io.github.tcdl.msb.threading; + +import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.support.Utils; +import org.apache.commons.lang3.RandomUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.stream.IntStream; + +/** + * This {@link MessageHandlerInvoker} implementation gives an ability to execute {@link io.github.tcdl.msb.MessageHandler} + * sequentially for messages with the same "groupId" (resolved by {@link MessageGroupStrategy} provided) while + * messages with different "groupId" could be processed in parallel. + */ +public class GroupedExecutorBasedMessageHandlerInvoker extends ExecutorBasedMessageHandlerInvoker { + + private static final Logger LOG = LoggerFactory.getLogger(GroupedExecutorBasedMessageHandlerInvoker.class); + + private final ExecutorService[] executors; + + private final MessageGroupStrategy messageGroupStrategy; + private final int numberOfThreads; + + public GroupedExecutorBasedMessageHandlerInvoker(int numberOfThreads, int queueCapacity, + ConsumerExecutorFactory consumerExecutorFactory, MessageGroupStrategy messageGroupStrategy) { + super(consumerExecutorFactory); + this.messageGroupStrategy = messageGroupStrategy; + this.numberOfThreads = numberOfThreads; + + executors = new ExecutorService[numberOfThreads]; + + IntStream + .range(0, numberOfThreads) + .forEach(i -> executors[i] = + consumerExecutorFactory.createConsumerThreadPool(1, queueCapacity)); + } + + @Override + protected void doSubmitTask(MessageProcessingTask task, Message message) { + int executorKey = getExecutorKey(message); + executors[executorKey].submit(task); + } + + private int getExecutorKey(Message message) { + Optional messageGroupId = messageGroupStrategy.getMessageGroupId(message); + if(messageGroupId.isPresent()) { + return Math.abs(messageGroupId.get() % numberOfThreads); + } else { + return RandomUtils.nextInt(0, numberOfThreads); + } + } + + @Override + public void shutdown() { + Arrays + .stream(executors) + .forEach(executor -> Utils.gracefulShutdown(executor, "consumer")); + } +} diff --git a/core/src/main/java/io/github/tcdl/msb/threading/MessageGroupStrategy.java b/core/src/main/java/io/github/tcdl/msb/threading/MessageGroupStrategy.java new file mode 100644 index 00000000..d96ffd0d --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/threading/MessageGroupStrategy.java @@ -0,0 +1,23 @@ +package io.github.tcdl.msb.threading; + +import io.github.tcdl.msb.api.message.Message; + +import java.util.Optional; + +/** + * Implementations of this interface define a way to resolve a message group by a message. Messages + * with the same message group will be executed by {@link GroupedExecutorBasedMessageHandlerInvoker} + * one after another (in a single-threaded mode) + * while messages with different message groups could be executed in parallel. + */ +@FunctionalInterface +public interface MessageGroupStrategy { + /** + * Resolve message group by a message. Message group identifier is any integer. If the message group + * can't be resolved for a particular message, {@link Optional#empty()} should be returned. In this + * case an execution thread will be selected randomly. + * @param message + * @return + */ + Optional getMessageGroupId(Message message); +} \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeStrategy.java b/core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvoker.java similarity index 84% rename from core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeStrategy.java rename to core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvoker.java index 9ecbf7b9..8db2a088 100644 --- a/core/src/main/java/io/github/tcdl/msb/adapters/MessageHandlerInvokeStrategy.java +++ b/core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvoker.java @@ -1,4 +1,4 @@ -package io.github.tcdl.msb.adapters; +package io.github.tcdl.msb.threading; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; @@ -6,9 +6,9 @@ import io.github.tcdl.msb.api.message.Message; /** - * Interface that defines a way to invoke {@link MessageHandler} to process a {@link Message} received. + * This interface defines a way to invoke {@link MessageHandler} to process a {@link Message} received. */ -public interface MessageHandlerInvokeStrategy { +public interface MessageHandlerInvoker { /** * Handle an incoming {@link Message} using {@link MessageHandler} provided. After an invocation attempt, one of * {@link AcknowledgementHandlerInternal} methods should be invoked (depending on result - @@ -23,4 +23,9 @@ public interface MessageHandlerInvokeStrategy { * @throws RuntimeException when a message can't be handled. */ void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler); + + /** + * Perform cleanup on shutdown if required. + */ + void shutdown(); } diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java b/core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java similarity index 76% rename from amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java rename to core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java index 5c172d28..ccc69f0b 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageProcessingTask.java +++ b/core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java @@ -1,4 +1,4 @@ -package io.github.tcdl.msb.adapters.amqp; +package io.github.tcdl.msb.threading; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; @@ -10,10 +10,10 @@ import java.util.Map; /** - * {@link AmqpMessageProcessingTask} wraps incoming message. This task is put into message processing thread pool (see {@link AmqpMessageConsumer}). + * {@link MessageProcessingTask} wraps incoming message. */ -public class AmqpMessageProcessingTask implements Runnable { - private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageProcessingTask.class); +public class MessageProcessingTask implements Runnable { + private static final Logger LOG = LoggerFactory.getLogger(MessageProcessingTask.class); final Message message; final MessageHandler messageHandler; @@ -21,7 +21,7 @@ public class AmqpMessageProcessingTask implements Runnable { final Map mdcLogContextMap; final boolean mdcLogCopy; - public AmqpMessageProcessingTask( MessageHandler messageHandler, Message message, + public MessageProcessingTask( MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal ackHandler) { this.message = message; this.messageHandler = messageHandler; @@ -54,4 +54,16 @@ public void run() { } } } + + public Message getMessage() { + return message; + } + + public MessageHandler getMessageHandler() { + return messageHandler; + } + + public AcknowledgementHandlerInternal getAckHandler() { + return ackHandler; + } } diff --git a/core/src/main/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvoker.java b/core/src/main/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvoker.java new file mode 100644 index 00000000..76a06987 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvoker.java @@ -0,0 +1,38 @@ +package io.github.tcdl.msb.threading; + +import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.support.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ExecutorService; + +/** + * Concurrent {@link MessageHandlerInvoker} implementation used to invoke all {@link io.github.tcdl.msb.MessageHandler} + * in a single thread pool with a configured number of threads. This approach is effective, but may lead + * to concurrent issues when incoming messages order matters. When facing this kind of issues, + * it is possible either to configure this class to work in a single-threaded mode, + * or use {@link GroupedExecutorBasedMessageHandlerInvoker} instead. + */ +public class ThreadPoolMessageHandlerInvoker extends ExecutorBasedMessageHandlerInvoker { + + private static final Logger LOG = LoggerFactory.getLogger(ThreadPoolMessageHandlerInvoker.class); + + private final ExecutorService executor; + + public ThreadPoolMessageHandlerInvoker(int numberOfThreads, int queueCapacity, ConsumerExecutorFactory consumerExecutorFactory) { + super(consumerExecutorFactory); + this.executor = consumerExecutorFactory.createConsumerThreadPool(numberOfThreads, queueCapacity); + } + + @Override + protected void doSubmitTask(MessageProcessingTask task, Message message) { + executor.submit(task); + } + + @Override + public void shutdown() { + Utils.gracefulShutdown(executor, "consumer"); + } + +} \ No newline at end of file diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 0b4ded99..fb3e0b23 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -15,6 +15,12 @@ msbConfig { brokerAdapterFactory = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory" + threadingConfig = { + consumerThreadPoolSize = 5 + # -1 means unlimited + consumerThreadPoolQueueCapacity = -1 + } + # Broker Adapter Defaults # AMQP (override values from .conf) brokerConfig = { diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java index 1e19fab5..495befd3 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java @@ -6,6 +6,9 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; + +import io.github.tcdl.msb.adapters.AdapterFactory; +import io.github.tcdl.msb.adapters.AdapterFactoryLoader; import io.github.tcdl.msb.api.RequesterResponderIT; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.collector.CollectorManager; @@ -18,6 +21,9 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import io.github.tcdl.msb.threading.ConsumerExecutorFactoryImpl; +import io.github.tcdl.msb.threading.MessageHandlerInvoker; +import io.github.tcdl.msb.threading.ThreadPoolMessageHandlerInvoker; import org.junit.Before; import org.junit.Test; @@ -36,7 +42,9 @@ public void setUp() { Clock clock = Clock.systemDefaultZone(); JsonValidator validator = new JsonValidator(); ObjectMapper messageMapper = TestUtils.createMessageMapper(); - this.channelManager = new ChannelManager(msbConfig, clock, validator, messageMapper); + MessageHandlerInvoker messageHandlerInvoker = new ThreadPoolMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(), new ConsumerExecutorFactoryImpl()); + AdapterFactory adapterFactory = new AdapterFactoryLoader(msbConfig).getAdapterFactory(); + this.channelManager = new ChannelManager(msbConfig, clock, validator, messageMapper, adapterFactory, messageHandlerInvoker); mockChannelMonitorAgent = mock(ChannelMonitorAgent.class); channelManager.setChannelMonitorAgent(mockChannelMonitorAgent); diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java index 4f6922aa..ec5aa233 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java @@ -7,6 +7,9 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; + +import io.github.tcdl.msb.adapters.AdapterFactory; +import io.github.tcdl.msb.adapters.AdapterFactoryLoader; import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.config.MsbConfig; @@ -20,6 +23,9 @@ import javax.xml.ws.Holder; +import io.github.tcdl.msb.threading.ConsumerExecutorFactoryImpl; +import io.github.tcdl.msb.threading.MessageHandlerInvoker; +import io.github.tcdl.msb.threading.ThreadPoolMessageHandlerInvoker; import org.junit.Before; import org.junit.Test; @@ -36,7 +42,11 @@ public void setUp() { Clock clock = Clock.systemDefaultZone(); JsonValidator validator = new JsonValidator(); ObjectMapper messageMapper = TestUtils.createMessageMapper(); - this.channelManager = new ChannelManager(msbConfig, clock, validator, messageMapper); + + MessageHandlerInvoker messageHandlerInvoker = new ThreadPoolMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(), new ConsumerExecutorFactoryImpl()); + + AdapterFactory adapterFactory = new AdapterFactoryLoader(msbConfig).getAdapterFactory(); + this.channelManager = new ChannelManager(msbConfig, clock, validator, messageMapper, adapterFactory, messageHandlerInvoker); mockChannelMonitorAgent = mock(ChannelMonitorAgent.class); channelManager.setChannelMonitorAgent(mockChannelMonitorAgent); diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index e58d5c14..d804a7f4 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -7,7 +7,7 @@ import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; -import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy; +import io.github.tcdl.msb.threading.MessageHandlerInvoker; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.MetaMessage; @@ -73,7 +73,7 @@ public class ConsumerTest { private MessageHandlerResolver consumedMessagesAwareMessageHandlerResolverMock; @Mock - private MessageHandlerInvokeStrategy messageHandlerInvokeStrategyMock; + private MessageHandlerInvoker messageHandlerInvokerMock; @Mock private AcknowledgementHandlerInternal acknowledgementHandlerMock; @@ -100,47 +100,47 @@ public void setUp() { @Test(expected = NullPointerException.class) public void testCreateConsumerNullAdapter() { - new Consumer(null, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + new Consumer(null, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullTopic() { - new Consumer(adapterMock, messageHandlerInvokeStrategyMock, null, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, null, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullMessageHandler() { - new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, null, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, null, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullMsbConf() { - new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, null, clock, channelMonitorAgentMock, validator, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, null, clock, channelMonitorAgentMock, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullClock() { - new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, null, channelMonitorAgentMock, validator, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, null, channelMonitorAgentMock, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullMonitorAgent() { - new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, null, validator, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, null, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullValidator() { - new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, null, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, null, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullMessageMapper() { - new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, null); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, null); } @Test public void testSubscribeAdapterSubscribed() { - new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); verify(adapterMock).subscribe(any(ConsumerAdapter.RawMessageHandler.class)); } @@ -148,7 +148,7 @@ public void testSubscribeAdapterSubscribed() { @Test public void testValidMessageProcessedBySubscriber() throws JsonConversionException { Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); @@ -158,7 +158,7 @@ public void testValidMessageProcessedBySubscriber() throws JsonConversionExcepti @Test public void testConsumedMessagesAwareMessageHandlerNotifiedWhenMessageHandled() throws JsonConversionException { Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, consumedMessagesAwareMessageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, consumedMessagesAwareMessageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); @@ -167,10 +167,10 @@ public void testConsumedMessagesAwareMessageHandlerNotifiedWhenMessageHandled() @Test public void testConsumedMessagesAwareMessageHandlerNotifiedWhenMessageLost() throws JsonConversionException { - doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokeStrategyMock).execute(any(), any(), any()); + doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokerMock).execute(any(), any(), any()); Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, consumedMessagesAwareMessageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, consumedMessagesAwareMessageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); @@ -184,7 +184,7 @@ public void testMessageHandlerCantBeResolved() throws JsonConversionException { .thenReturn(Optional.empty()); Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); @@ -194,10 +194,10 @@ public void testMessageHandlerCantBeResolved() throws JsonConversionException { @Test public void testMessageHandlerInvokeException() throws JsonConversionException { - doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokeStrategyMock).execute(any(), any(), any()); + doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokerMock).execute(any(), any(), any()); Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); @@ -206,7 +206,7 @@ public void testMessageHandlerInvokeException() throws JsonConversionException { @Test public void testExceptionWhileMessageConvertingProcessedBySubscriber() throws JsonConversionException { - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock); @@ -220,7 +220,7 @@ public void testHandleRawMessageConsumeFromTopicSkipValidation() { // disable validation when(msbConf.isValidateMessage()).thenReturn(false); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); // create a message with required empty namespace Message message = TestUtils.createSimpleRequestMessage(""); @@ -235,7 +235,7 @@ public void testHandleRawMessageConsumeFromTopicSkipValidation() { @Test public void testHandleRawMessageConsumeFromTopic() throws JsonConversionException { Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); verifyMessageHandled(); @@ -244,7 +244,7 @@ public void testHandleRawMessageConsumeFromTopic() throws JsonConversionExceptio @Test public void testHandleRawMessageConsumeFromTopicValidateThrowException() { MsbConfig msbConf = TestUtils.createMsbConfigurations(); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock); verifyMessageNotHandled(); @@ -254,7 +254,7 @@ public void testHandleRawMessageConsumeFromTopicValidateThrowException() { public void testHandleRawMessageConsumeFromServiceTopicValidateThrowException() { String service_topic = "_service:topic"; MsbConfig msbConf = TestUtils.createMsbConfigurations(); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, service_topic, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, service_topic, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock); verifyMessageNotHandled(); @@ -263,7 +263,7 @@ public void testHandleRawMessageConsumeFromServiceTopicValidateThrowException() @Test public void testHandleRawMessageConsumeFromTopicExpiredMessage() throws JsonConversionException { Message expiredMessage = createExpiredMsbRequestMessageWithTopicTo(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokeStrategyMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper), acknowledgementHandlerMock); verifyMessageNotHandled(); @@ -295,21 +295,30 @@ private void verifyMdc(boolean isMdcExpected, boolean isSplitExpected) { Message originalMessage = TestUtils.createMsbRequestMessage( TOPIC, null, CORRELATION_ID, TestUtils.createSimpleRequestPayload(), "tag1", splitTag, "tag3"); - MessageHandlerInvokeStrategy testInvokeStrategy = (messageHandler, message, acknowledgeHandler) -> { - if(isMdcExpected) { - assertEquals("tag1,"+splitTag+",tag3", MDC.get(MDC_KEY_TAGS)); - assertEquals(CORRELATION_ID, MDC.get(MDC_KEY_CORR_ID)); + MessageHandlerInvoker testInvokeStrategy = new MessageHandlerInvoker() { - } else { - assertTrue(StringUtils.isEmpty(MDC.get(MDC_KEY_TAGS))); - assertTrue(StringUtils.isEmpty(MDC.get(MDC_KEY_CORR_ID))); + @Override + public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) { + if(isMdcExpected) { + assertEquals("tag1,"+splitTag+",tag3", MDC.get(MDC_KEY_TAGS)); + assertEquals(CORRELATION_ID, MDC.get(MDC_KEY_CORR_ID)); + } else { + assertTrue(StringUtils.isEmpty(MDC.get(MDC_KEY_TAGS))); + assertTrue(StringUtils.isEmpty(MDC.get(MDC_KEY_CORR_ID))); + + } + + if(isSplitExpected) { + assertEquals(splitTagVal, MDC.get(MDC_SPLIT_KEY)); + } else { + assertTrue(StringUtils.isEmpty(MDC.get(MDC_SPLIT_KEY))); + } } - if(isSplitExpected) { - assertEquals(splitTagVal, MDC.get(MDC_SPLIT_KEY)); - } else { - assertTrue(StringUtils.isEmpty(MDC.get(MDC_SPLIT_KEY))); + @Override + public void shutdown() { + } }; @@ -333,10 +342,10 @@ private Message createExpiredMsbRequestMessageWithTopicTo(String topicTo) { } private void verifyMessageHandled() { - verify(messageHandlerInvokeStrategyMock, times(1)).execute(eq(messageHandlerMock), any(Message.class), eq(acknowledgementHandlerMock)); + verify(messageHandlerInvokerMock, times(1)).execute(eq(messageHandlerMock), any(Message.class), eq(acknowledgementHandlerMock)); } private void verifyMessageNotHandled() { - verify(messageHandlerInvokeStrategyMock, never()).execute(any(), any(), any()); + verify(messageHandlerInvokerMock, never()).execute(any(), any(), any()); } } \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java index 98bfff13..f829a044 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java @@ -2,10 +2,8 @@ import io.github.tcdl.msb.adapters.AdapterFactory; import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.adapters.MessageHandlerInvokeStrategy; import io.github.tcdl.msb.adapters.ProducerAdapter; import io.github.tcdl.msb.config.MsbConfig; -import io.github.tcdl.msb.impl.SimpleMessageHandlerInvokeStrategyImpl; /** * This AdapterFactory implementation is used to capture/submit raw messages as JSON and could be used during testing. @@ -42,8 +40,8 @@ public ConsumerAdapter createConsumerAdapter(String namespace, boolean isRespons } @Override - public MessageHandlerInvokeStrategy createMessageHandlerInvokeStrategy(String topic) { - return new SimpleMessageHandlerInvokeStrategyImpl(); + public boolean isUseMsbThreadingModel() { + return false; } @Override diff --git a/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java b/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java index 8b17e398..15a88016 100644 --- a/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java +++ b/core/src/test/java/io/github/tcdl/msb/support/TestUtils.java @@ -6,6 +6,8 @@ import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigValueFactory; import io.github.tcdl.msb.ChannelManager; +import io.github.tcdl.msb.adapters.AdapterFactory; +import io.github.tcdl.msb.adapters.AdapterFactoryLoader; import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.MsbContextBuilder; import io.github.tcdl.msb.api.ObjectFactory; @@ -23,6 +25,10 @@ import io.github.tcdl.msb.impl.MsbContextImpl; import io.github.tcdl.msb.impl.ObjectFactoryImpl; import io.github.tcdl.msb.message.MessageFactory; +import io.github.tcdl.msb.threading.ConsumerExecutorFactoryImpl; +import io.github.tcdl.msb.threading.DirectMessageHandlerInvoker; +import io.github.tcdl.msb.threading.MessageHandlerInvoker; +import io.github.tcdl.msb.threading.ThreadPoolMessageHandlerInvoker; import java.io.IOException; import java.time.Clock; @@ -110,6 +116,22 @@ public static Message createMsbRequestMessageNoPayload(String namespace, String .build(); } + public static Message createMsbForwardMessageNoPayload(String namespace, String forwardTopic) { + MsbConfig msbConf = createMsbConfigurations(); + Clock clock = Clock.systemDefaultZone(); + + Topics topic = new Topics(namespace, null, forwardTopic); + + MetaMessage.Builder metaBuilder = createSimpleMetaBuilder(msbConf, clock); + return new Message.Builder() + .withCorrelationId(Utils.generateId()) + .withId(Utils.generateId()) + .withTopics(topic) + .withMetaBuilder(metaBuilder) + .withPayload(null) + .build(); + } + public static Message createMsbRequestMessageWithCorrelationId(String topicTo, String correlationId, RestPayload payload) { ObjectMapper payloadMapper = createMessageMapper(); JsonNode payloadNode = Utils.convert(payload, JsonNode.class, payloadMapper); @@ -417,7 +439,8 @@ public MsbContextImpl build() { MsbConfig msbConfig = msbConfigOp.orElse(TestUtils.createMsbConfigurations()); Clock clock = clockOp.orElse(Clock.systemDefaultZone()); ObjectMapper messageMapper = createMessageMapper(); - ChannelManager channelManager = channelManagerOp.orElseGet(() -> new ChannelManager(msbConfig, clock, new JsonValidator(), messageMapper)); + ChannelManager channelManager = channelManagerOp.orElseGet(() -> new ChannelManager( + msbConfig, clock, new JsonValidator(), messageMapper, new AdapterFactoryLoader(msbConfig).getAdapterFactory(), new DirectMessageHandlerInvoker())); MessageFactory messageFactory = messageFactoryOp.orElseGet(() -> new MessageFactory(msbConfig.getServiceDetails(), clock, messageMapper)); TimeoutManager timeoutManager = timeoutManagerOp.orElseGet(() -> new TimeoutManager(1)); CollectorManagerFactory collectorManagerFactory = collectorManagerFactoryOp.orElseGet(() -> new CollectorManagerFactory(channelManager)); diff --git a/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java b/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java new file mode 100644 index 00000000..b3843188 --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java @@ -0,0 +1,61 @@ +package io.github.tcdl.msb.threading; + +import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.config.MsbConfig; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; + +import io.github.tcdl.msb.threading.ExecutorBasedMessageHandlerInvoker; +import io.github.tcdl.msb.threading.MessageProcessingTask; +import io.github.tcdl.msb.threading.ThreadPoolMessageHandlerInvoker; +import org.junit.Before; +import org.junit.Test; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.*; + +@RunWith(MockitoJUnitRunner.class) +public class ConsumerExecutorFactoryTest { + + ConsumerExecutorFactoryImpl factory; + + @Before + public void setUp() throws Exception { + factory = new ConsumerExecutorFactoryImpl(); + } + + @Test + public void testCreateConsumerThreadPoolBoundedQueue() { + ExecutorService consumerThreadPool = factory.createConsumerThreadPool(5, 20); + + assertNotNull(consumerThreadPool); + assertTrue(consumerThreadPool instanceof ThreadPoolExecutor); + ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) consumerThreadPool; + assertEquals(5, threadPoolExecutor.getCorePoolSize()); + BlockingQueue queue = threadPoolExecutor.getQueue(); + assertNotNull(queue); + assertTrue(queue instanceof ArrayBlockingQueue); + assertEquals(20, ((ArrayBlockingQueue) queue).remainingCapacity()); + } + + @Test + public void testCreateConsumerThreadPoolUnboundedQueue() { + ExecutorService consumerThreadPool = factory.createConsumerThreadPool(5, -1); + + assertNotNull(consumerThreadPool); + assertTrue(consumerThreadPool instanceof ThreadPoolExecutor); + ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) consumerThreadPool; + assertEquals(5, threadPoolExecutor.getCorePoolSize()); + + BlockingQueue queue = threadPoolExecutor.getQueue(); + assertNotNull(queue); + assertTrue(queue instanceof LinkedBlockingQueue); + } +} diff --git a/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImplTest.java b/core/src/test/java/io/github/tcdl/msb/threading/DirectMessageHandlerInvokerTest.java similarity index 78% rename from core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImplTest.java rename to core/src/test/java/io/github/tcdl/msb/threading/DirectMessageHandlerInvokerTest.java index 04cf8cca..e08811b0 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/SimpleMessageHandlerInvokeStrategyImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/threading/DirectMessageHandlerInvokerTest.java @@ -1,4 +1,4 @@ -package io.github.tcdl.msb.impl; +package io.github.tcdl.msb.threading; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; @@ -8,10 +8,12 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import static org.mockito.Mockito.*; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) -public class SimpleMessageHandlerInvokeStrategyImplTest { +public class DirectMessageHandlerInvokerTest { @Mock MessageHandler messageHandler; @@ -22,7 +24,7 @@ public class SimpleMessageHandlerInvokeStrategyImplTest { Message message; @InjectMocks - SimpleMessageHandlerInvokeStrategyImpl strategy; + DirectMessageHandlerInvoker strategy; @Test public void testDirectInvoke() { @@ -31,4 +33,4 @@ public void testDirectInvoke() { verify(acknowledgeHandler, times(1)).autoConfirm(); } -} +} \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvokerTest.java b/core/src/test/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvokerTest.java new file mode 100644 index 00000000..b4e00e6a --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvokerTest.java @@ -0,0 +1,161 @@ +package io.github.tcdl.msb.threading; + +import io.github.tcdl.msb.MessageHandler; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; +import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.config.MsbConfig; +import io.github.tcdl.msb.support.TestUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class GroupedExecutorBasedMessageHandlerInvokerTest { + private static final int CONFIG_THREADS = 5; + private static final int CONFIG_QUEUE = -1; + + ExecutorService[] executors; + + @Mock + AcknowledgementHandlerInternal acknowledgeHandler; + + @Mock + MessageHandler messageHandler; + + @Mock + ConsumerExecutorFactory consumerExecutorFactory; + + @Mock + MsbConfig msbConfig; + + @Mock + MessageGroupStrategy messageGroupStrategy; + + Message message = TestUtils.createMsbRequestMessage("any0","any0"); + + Message message1 = TestUtils.createMsbRequestMessage("any1","any1"); + + Message message2 = TestUtils.createMsbRequestMessage("any2","any2"); + + GroupedExecutorBasedMessageHandlerInvoker invoker; + + @Before + public void setUp() throws Exception { + + executors = new ExecutorService[CONFIG_THREADS]; + + for(int i = 0; i< CONFIG_THREADS ; i++) { + executors[i] = mock(ExecutorService.class); + when(executors[i].awaitTermination(10, TimeUnit.SECONDS)).thenReturn(true); + } + + when(msbConfig.getConsumerThreadPoolSize()).thenReturn(CONFIG_THREADS); + when(msbConfig.getConsumerThreadPoolQueueCapacity()).thenReturn(CONFIG_QUEUE); + + when(consumerExecutorFactory.createConsumerThreadPool(1, CONFIG_QUEUE)) + .thenReturn(executors[0], Arrays.copyOfRange(executors, 1, CONFIG_THREADS)); + + invoker = new GroupedExecutorBasedMessageHandlerInvoker(CONFIG_THREADS, CONFIG_QUEUE, consumerExecutorFactory, messageGroupStrategy); + + when(messageGroupStrategy.getMessageGroupId(message)).thenReturn(Optional.of(0)); + when(messageGroupStrategy.getMessageGroupId(message1)).thenReturn(Optional.of(1)); + when(messageGroupStrategy.getMessageGroupId(message2)).thenReturn(Optional.of(2)); + } + + @Test + public void testExecutorsInitialized() { + verify(consumerExecutorFactory, times(CONFIG_THREADS)).createConsumerThreadPool(1, CONFIG_QUEUE); + } + + @Test + public void testMessageHandling() { + invoker.execute(messageHandler, message, acknowledgeHandler); + verify(messageHandler, never()).handleMessage(any(), any()); + ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(MessageProcessingTask.class); + verify(executors[0], times(1)).submit(taskCaptor.capture()); + MessageProcessingTask task = taskCaptor.getValue(); + + assertEquals(message, task.getMessage()); + assertEquals(messageHandler, task.getMessageHandler()); + assertEquals(acknowledgeHandler, task.getAckHandler()); + } + + @Test + public void testMessageRouting() { + invoker.execute(messageHandler, message, acknowledgeHandler); + verify(executors[0], times(1)).submit(any(MessageProcessingTask.class)); + + verify(executors[1], times(0)).submit(any(MessageProcessingTask.class)); + + invoker.execute(messageHandler, message2, acknowledgeHandler); + verify(executors[2], times(1)).submit(any(MessageProcessingTask.class)); + } + + @Test + public void testMessageRoutingGroupOverflow() { + when(messageGroupStrategy.getMessageGroupId(message)).thenReturn(Optional.of(CONFIG_THREADS * 1231 + 3)); + + invoker.execute(messageHandler, message, acknowledgeHandler); + verify(executors[3], times(1)).submit(any(MessageProcessingTask.class)); + } + + public void testMessageRoutingGroupNegative() { + when(messageGroupStrategy.getMessageGroupId(message)).thenReturn(Optional.of(-1)); + + invoker.execute(messageHandler, message, acknowledgeHandler); + verify(executors[CONFIG_THREADS - 1], times(1)).submit(any(MessageProcessingTask.class)); + } + + @Test + public void testMessageRoutingGroupMissing() { + when(messageGroupStrategy.getMessageGroupId(message)).thenReturn(Optional.empty()); + + Set executorsInvolved = new HashSet<>(); + AtomicInteger invocationsCount = new AtomicInteger(0); + + Arrays.stream(executors).forEach((executor) -> + when(executor.submit(any(MessageProcessingTask.class))) + .thenAnswer((invocation) -> { + executorsInvolved.add(executor); + invocationsCount.incrementAndGet(); + return null; + })); + + int executeCount = 0; + int maxIterations = 5000; + do { + executeCount ++; + invoker.execute(messageHandler, message, acknowledgeHandler); + if(executeCount > maxIterations) { + fail(String.format("All available executors should be involved when message group is defined," + + " failed to check this requirement in %d iterations", maxIterations)); + } + } while (executorsInvolved.size() < CONFIG_THREADS); + + assertEquals(executeCount, invocationsCount.get()); + assertEquals(CONFIG_THREADS, executorsInvolved.size()); + } + + @Test + public void testShutdown() { + Arrays.stream(executors).forEach((executor)->verify(executor, times(0)).shutdown()); + invoker.shutdown(); + Arrays.stream(executors).forEach((executor)->verify(executor, times(1)).shutdown()); + } +} \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvokerImplTest.java b/core/src/test/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvokerImplTest.java new file mode 100644 index 00000000..478515e0 --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvokerImplTest.java @@ -0,0 +1,81 @@ +package io.github.tcdl.msb.threading; + +import com.typesafe.config.ConfigFactory; +import io.github.tcdl.msb.MessageHandler; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; +import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.config.MsbConfig; +import io.github.tcdl.msb.support.TestUtils; +import io.github.tcdl.msb.threading.ThreadPoolMessageHandlerInvoker; +import io.github.tcdl.msb.threading.MessageProcessingTask; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class ThreadPoolMessageHandlerInvokerImplTest { + + private static final int CONFIG_THREADS = 5; + private static final int CONFIG_QUEUE = -1; + + @Mock + ExecutorService mockExecutor; + + @Mock + AcknowledgementHandlerInternal acknowledgeHandler; + + @Mock + MessageHandler messageHandler; + + @Mock + ConsumerExecutorFactory consumerExecutorFactory; + + Message message = TestUtils.createMsbRequestMessage("any","any"); + + ThreadPoolMessageHandlerInvoker invoker; + + @Before + public void setUp() throws Exception { + + when(consumerExecutorFactory.createConsumerThreadPool(CONFIG_THREADS, CONFIG_QUEUE)).thenReturn(mockExecutor); + + invoker = new ThreadPoolMessageHandlerInvoker(CONFIG_THREADS, CONFIG_QUEUE, consumerExecutorFactory); + + when(mockExecutor.awaitTermination(10, TimeUnit.SECONDS)).thenReturn(true); + } + + @Test + public void testSingleExecutorInitialized() { + verify(consumerExecutorFactory, times(1)).createConsumerThreadPool(CONFIG_THREADS, CONFIG_QUEUE); + } + + @Test + public void testMessageHandling() { + invoker.execute(messageHandler, message, acknowledgeHandler); + verify(messageHandler, never()).handleMessage(any(), any()); + ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(MessageProcessingTask.class); + verify(mockExecutor, times(1)).submit(taskCaptor.capture()); + MessageProcessingTask task = taskCaptor.getValue(); + + assertEquals(message, task.getMessage()); + assertEquals(messageHandler, task.getMessageHandler()); + assertEquals(acknowledgeHandler, task.getAckHandler()); + } + + @Test + public void testShutdown() { + verify(mockExecutor, times(0)).shutdown(); + invoker.shutdown(); + verify(mockExecutor, times(1)).shutdown(); + } +} \ No newline at end of file diff --git a/doc/MSB.md b/doc/MSB.md index a0dd3ebe..204b3732 100644 --- a/doc/MSB.md +++ b/doc/MSB.md @@ -333,6 +333,22 @@ The nested section `messageKeys`: `correlationId` - Mapped Diagnostic Context key for message correlationId. Defaults to `msbCorrelationId`. +### Description of multithreading configuration +The section `threadingConfig` from [reference.conf](/core/src/main/resources/reference.conf). + +`consumerThreadPoolSize` – number of consumer threads used to process incoming messages. +Defines the level of parallelism. Default is 5. + +`consumerThreadPoolQueueCapacity` – maximum number of requests waiting in FIFO queue to be processed by consumer thread pool. +Incoming messages will be discarded in case of the exceeded limit. +Should be positive integer or -1. Value of -1 stands for unlimited. The default value is -1. + +Please take into an account, then when handling messages in multithreaded mode (with `consumerThreadPoolSize` other than 1), +incoming messages could be processed out of incoming topic order. If the order of incoming messages matters: + - Use `consumerThreadPoolSize` = 1 - process all incoming messages in a single-threaded mode; + - Use MsbContextBuilder.withMessageGroupStrategy() - so messages with the same "groupId" will be processed + in a single-threaded mode while messages with different "groupId" could be processed in parallel. + ### Description of AMQP connection configuration fields The _key values pairs_ described in this section are specific for the chosen Broker. The section `brokerConfig` from [reference.conf](/core/src/main/resources/reference.conf) file override values from [amqp.conf](/amqp/src/main/resources/amqp.conf). @@ -355,10 +371,6 @@ Specifies types of the queue used for incomming messages (except for responses t See for more [detail](https://www.rabbitmq.com/tutorials/amqp-concepts.html). -`consumerThreadPoolSize` – number of consumer threads used to process incoming messages. Defines the level of parallelism. Default is 5. - -`consumerThreadPoolQueueCapacity` – maximum number of requests waiting in FIFO queue to be processed by consumer thread pool. Should be positive integer or -1. Value of -1 stands for unlimited. The default value is -1. - The following fields are optional in case of broker running on local machine but are mandatory when using broker on remote computer. When there is a need to override the default values these fields are specified in application.conf file as additional `brokerConfig` parameters. diff --git a/release-notes.html b/release-notes.html index 3527c1d5..1c154c79 100644 --- a/release-notes.html +++ b/release-notes.html @@ -4,13 +4,18 @@ -

Welcome to MSB-Java version 1.4.6

+

Welcome to MSB-Java version 1.4.7

April 12, 2016

 
-------------------------------------------------------------------------------------
+Features of MSB-Java version 1.4.7:
+   - Multithreading settings parameters consumerThreadPoolSize, consumerThreadPoolQueueCapacity moved
+    from "brokerConfig" to "threadingConfig" config section.
+   - Now it is possible to process a group of messages in a single-threaded mode using
+    MsbContextBuilder.withMessageGroupStrategy().
+
 Features of MSB-Java version 1.4.6:
    - Updated messages schema validation;
    - Fixed issue with unresolved host name;

From 81f96771f1d113a3d06a702b1197fd77f95deb90 Mon Sep 17 00:00:00 2001
From: alex 
Date: Fri, 20 May 2016 18:42:17 +0300
Subject: [PATCH 122/226] Attempt to fix blinking RequesterResponderIT

---
 .../java/io/github/tcdl/msb/api/RequesterResponderIT.java   | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java
index dcc18957..6aa7fea7 100644
--- a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java
+++ b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java
@@ -238,15 +238,13 @@ public void testMultipleRequesterListenForAcks() throws Exception {
             }
 
         });
-        publishingThread.setDaemon(true);
-        publishingThread.start();
 
         //listen for message and send ack
         MsbContextImpl serverMsbContext = TestUtils.createSimpleMsbContext();
         storage.connect(serverMsbContext);
 
         Random randomAckValue = new Random();
-        randomAckValue.ints();
+
         serverMsbContext.getObjectFactory().createResponderServer(namespace, requestOptions.getMessageTemplate(), 
                 (request, responderContext) -> {
                     responderContext.getResponder().sendAck(randomAckValue.nextInt(), randomAckValue.nextInt());
@@ -254,6 +252,8 @@ public void testMultipleRequesterListenForAcks() throws Exception {
                 })
                 .listen();
 
+        publishingThread.start();
+
         assertTrue("Message ack was not send", ackSend.await(MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS));
         assertTrue("Message ack response not received", ackResponseReceived.await(MESSAGE_ROUNDTRIP_TRANSMISSION_TIME, TimeUnit.MILLISECONDS));
         assertTrue("Expected one ack", receivedResponseAcks.size() == requestsToSendDuringTest);

From 481fb0a93e222b494aa412ef9f62e78b75e056a1 Mon Sep 17 00:00:00 2001
From: alex 
Date: Tue, 17 May 2016 13:33:16 +0300
Subject: [PATCH 123/226] API enhancement. Added bunch of overloaded request()
 methods in Requester. Added two convenience methods in ObjectFactory.

---
 .../tcdl/msb/acceptance/MsbTestHelper.java    |  18 +-
 .../bdd/steps/RequesterResponderSteps.java    | 206 +++++++++++++-----
 .../scenarios/requester_responder.story       |  29 +++
 .../io/github/tcdl/msb/api/ObjectFactory.java |  17 ++
 .../github/tcdl/msb/api/RequestOptions.java   |  13 ++
 .../io/github/tcdl/msb/api/Requester.java     |  38 ++++
 .../github/tcdl/msb/collector/Collector.java  |   6 +-
 .../io/github/tcdl/msb/config/MsbConfig.java  |  17 ++
 .../tcdl/msb/impl/ObjectFactoryImpl.java      |  39 ++++
 .../github/tcdl/msb/impl/RequesterImpl.java   |  66 +++++-
 .../github/tcdl/msb/impl/ResponderImpl.java   |   4 -
 core/src/main/resources/reference.conf        |   5 +
 .../tcdl/msb/api/RequestOptionsTest.java      |  28 +++
 .../tcdl/msb/impl/ObjectFactoryImplTest.java  |   7 +
 .../objectfactory/TestMsbObjectFactory.java   |  41 +++-
 15 files changed, 444 insertions(+), 90 deletions(-)

diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
index fc0472f7..2f1db527 100644
--- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
+++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
@@ -1,12 +1,8 @@
 package io.github.tcdl.msb.acceptance;
 
-import io.github.tcdl.msb.api.Callback;
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.MsbContext;
-import io.github.tcdl.msb.api.MsbContextBuilder;
-import io.github.tcdl.msb.api.RequestOptions;
-import io.github.tcdl.msb.api.Requester;
-import io.github.tcdl.msb.api.ResponderServer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.typesafe.config.Config;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.impl.MsbContextImpl;
@@ -14,9 +10,7 @@
 
 import java.util.HashMap;
 import java.util.Map;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.typesafe.config.Config;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * Utility to simplify using requester and responder server
@@ -141,6 +135,10 @@ public  void sendRequest(Requester requester, Object payload, boolean wait
         requester.publish(payload, tag);
     }
 
+    public  CompletableFuture sendForResult(Requester requester, Object payload, String... tags){
+        return requester.request(payload, tags);
+    }
+
     public ResponderServer createResponderServer(String namespace, ResponderServer.RequestHandler requestHandler) {
         return createResponderServer(DEFAULT_CONTEXT_NAME, namespace, requestHandler);
     }
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 08d5f163..2dabaa6c 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -1,18 +1,9 @@
 package io.github.tcdl.msb.acceptance.bdd.steps;
 
-import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import io.github.tcdl.msb.api.Requester;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.support.Utils;
-
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
-
 import org.hamcrest.Matchers;
 import org.jbehave.core.annotations.Given;
 import org.jbehave.core.annotations.Then;
@@ -21,10 +12,18 @@
 import org.jbehave.core.model.OutcomesTable;
 import org.junit.Assert;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME;
+import static org.junit.Assert.fail;
 
 /**
- * Steps to send requests and respond with predifined responses
+ * Steps to send requests and respond with predefined responses
  */
 public class RequesterResponderSteps extends MsbSteps {
 
@@ -42,6 +41,12 @@ public class RequesterResponderSteps extends MsbSteps {
     private volatile int responsesToSendCount;
     private volatile int responsesToExpectCount;
     private volatile String latestForwardNamespace = null;
+    private CompletableFuture lastFutureResult = null;
+    private RestPayload resolvedResponse = null;
+    private final LinkedList> responses = new LinkedList<>();
+    private final String ACK = "ACK";
+    private final String PAYLOAD = "PAYLOAD";
+    private final int ACK_TIMEOUT = 500;
 
     public Optional getDefaultRequestsAckType() {
         return defaultRequestsAckType;
@@ -53,53 +58,85 @@ public void createResponderServer(String namespace) {
         createResponderServer(DEFAULT_CONTEXT_NAME, namespace);
     }
 
+    @Given("responder server responds sequentially on namespace $namespace")
+    public void respondSequentially(String namespace) throws Exception {
+        ObjectMapper mapper = helper.getPayloadMapper(DEFAULT_CONTEXT_NAME);
+        helper.createResponderServer(DEFAULT_CONTEXT_NAME, namespace, (request, responderContext) -> {
+            if (responses.isEmpty()) {
+                return;
+            }
+
+            responses.forEach(nextResponse -> nextResponse.entrySet().stream().findFirst().ifPresent(entry -> {
+                switch (entry.getKey()) {
+                    case ACK:
+                        Integer responsesRemaining = (Integer) (entry.getValue());
+                        responderContext.getResponder().sendAck(ACK_TIMEOUT, responsesRemaining);
+                        try {
+                            TimeUnit.MILLISECONDS.sleep(ACK_TIMEOUT);
+                        } catch (InterruptedException e) {
+                            throw new RuntimeException(e);
+                        }
+                        break;
+                    case PAYLOAD:
+                        RestPayload payload = new RestPayload.Builder()
+                                .withBody(Utils.fromJson(responseBody, Map.class, mapper))
+                                .build();
+                        responderContext.getResponder().send(payload);
+                        break;
+                }
+            }));
+        }).listen();
+    }
+
     @Given("responder server from $contextName listens on namespace $namespace")
     @When("responder server from $contextName listens on namespace $namespace")
     public void createResponderServer(String contextName, String namespace) {
         beforeCreateResponder();
         ObjectMapper mapper = helper.getPayloadMapper(contextName);
         helper.createResponderServer(contextName, namespace, (request, responderContext) -> {
-            if (responseBody != null) {
-                countRequestsReceived.incrementAndGet();
-
-                Runnable responseActions = () -> {
-                    boolean isSendResponse = true;
-                    String ackType = nextRequestAckType.orElseGet(
-                            () -> defaultRequestsAckType.orElseGet(
-                                    () -> "auto"));
-
-                    switch (ackType) {
-                        case "confirm":
-                            responderContext.getAcknowledgementHandler().confirmMessage();
-                            break;
-                        case "reject":
-                            responderContext.getAcknowledgementHandler().rejectMessage();
-                            isSendResponse = false;
-                            break;
-                        case "retry":
-                            responderContext.getAcknowledgementHandler().retryMessage();
-                            isSendResponse = false;
-                            break;
-                    }
+            if (responseBody == null) {
+                return;
+            }
 
-                    nextRequestAckType = Optional.empty();
-                    latestForwardNamespace = responderContext.getOriginalMessage().getTopics().getForward();
-                    if (isSendResponse) {
-                        RestPayload payload = new RestPayload.Builder()
-                                .withBody(Utils.fromJson(responseBody, Map.class, mapper))
-                                .build();
-                        for(int i=0; i< responsesToSendCount; i++) {
-                            responderContext.getResponder().send(payload);
-                        }
-                    }
-                };
+            countRequestsReceived.incrementAndGet();
+
+            Runnable responseActions = () -> {
+                boolean isSendResponse = true;
+                String ackType = nextRequestAckType.orElseGet(
+                        () -> defaultRequestsAckType.orElseGet(
+                                () -> "auto"));
+
+                switch (ackType) {
+                    case "confirm":
+                        responderContext.getAcknowledgementHandler().confirmMessage();
+                        break;
+                    case "reject":
+                        responderContext.getAcknowledgementHandler().rejectMessage();
+                        isSendResponse = false;
+                        break;
+                    case "retry":
+                        responderContext.getAcknowledgementHandler().retryMessage();
+                        isSendResponse = false;
+                        break;
+                }
 
-                if(isResponseInNewThread) {
-                    responderContext.getAcknowledgementHandler().setAutoAcknowledgement(false);
-                    new Thread(responseActions).run();
-                } else {
-                    responseActions.run();
+                nextRequestAckType = Optional.empty();
+                latestForwardNamespace = responderContext.getOriginalMessage().getTopics().getForward();
+                if (isSendResponse) {
+                    RestPayload payload = new RestPayload.Builder()
+                            .withBody(Utils.fromJson(responseBody, Map.class, mapper))
+                            .build();
+                    for (int i = 0; i < responsesToSendCount; i++) {
+                        responderContext.getResponder().send(payload);
+                    }
                 }
+            };
+
+            if (isResponseInNewThread) {
+                responderContext.getAcknowledgementHandler().setAutoAcknowledgement(false);
+                new Thread(responseActions).run();
+            } else {
+                responseActions.run();
             }
         }).listen();
     }
@@ -211,7 +248,7 @@ private void onBeforeRequest() {
     }
 
     private void onResponse(RestPayload> payload) {
-        if(responseProcessingDelay > 0) {
+        if (responseProcessingDelay > 0) {
             try {
                 Thread.sleep(responseProcessingDelay);
             } catch (InterruptedException e) {
@@ -219,7 +256,7 @@ private void onResponse(RestPayload>
             }
         }
 
-        if(responseCountDown != null) {
+        if (responseCountDown != null) {
             responseCountDown.countDown();
         }
 
@@ -228,8 +265,8 @@ private void onResponse(RestPayload>
     }
 
     private void onEnd(Void in) {
-        if(responseCountDown != null && responseCountDown.getCount() > 0) {
-            Assert.fail("onEnd has been executed while not all responses were received yet, pending responses count: " + responseCountDown.getCount());
+        if (responseCountDown != null && responseCountDown.getCount() > 0) {
+            fail("onEnd has been executed while not all responses were received yet, pending responses count: " + responseCountDown.getCount());
         }
     }
 
@@ -238,15 +275,15 @@ public void waitForResponse(long timeout) throws Exception {
         try {
             receivedResponse = receivedResponseFuture.get(timeout, TimeUnit.MILLISECONDS);
         } catch (TimeoutException timeoutException) {
-            Assert.fail("Response has not been received during a timeout");
+            fail("Response has not been received during a timeout");
         }
         Assert.assertNotNull("Response received is null", receivedResponse);
     }
 
     @Then("requester will get all responses in $timeout ms")
     public void waitForAllResponses(long timeout) throws Exception {
-        if(!responseCountDown.await(timeout, TimeUnit.MILLISECONDS)) {
-            Assert.fail("All responses has not been received during a timeout, pending count: " + responseCountDown.getCount());
+        if (!responseCountDown.await(timeout, TimeUnit.MILLISECONDS)) {
+            fail("All responses has not been received during a timeout, pending count: " + responseCountDown.getCount());
         }
     }
 
@@ -298,4 +335,59 @@ public void responseContains(ExamplesTable table) throws Exception {
 
         outcomes.verify();
     }
+
+    @When("requester sends a request for single result to namespace $namespace")
+    public void requestForSingleResult(String namespace) throws Exception {
+        String query = "QUERY";
+        String body = "body";
+        RestPayload payload = helper.createFacetParserPayload(query, body);
+        requester = helper.createRequester(namespace, 1, RestPayload.class);
+        lastFutureResult = helper.sendForResult(requester, payload);
+    }
+
+    @When("requester blocks waiting for response for $timeout ms")
+    public void blockUntilResponseReceived(int timeout) throws Exception {
+        resolvedResponse = lastFutureResult.get(timeout, TimeUnit.MILLISECONDS);
+    }
+
+    @Then("resolved response equals $table")
+    public void verifyFutureResult(ExamplesTable table) {
+        Map expected = table.getRow(0);
+        OutcomesTable outcomes = new OutcomesTable();
+
+        for (String key : expected.keySet()) {
+            outcomes.addOutcome(key, resolvedResponse.getBody().toString(), Matchers.containsString(expected.get(key)));
+        }
+
+        outcomes.verify();
+    }
+
+    @Given("next response from responder contains acknowledge with $remaining remaining response")
+    public void addAckToResponsesQueue(int remainingResponses) {
+        Map request = new HashMap<>();
+        request.put(ACK, remainingResponses);
+        responses.add(request);
+    }
+
+    @Given("next response from responder contains body $responseBody")
+    public void addBodyToResponsesQueue(String responseBody) {
+        Map request = new HashMap<>();
+        request.put(PAYLOAD, responseBody);
+        responses.add(request);
+    }
+
+    @Then("requester gets exception when tries to obtain result")
+    public void assertExceptionOccured() throws Exception {
+        try {
+            resolvedResponse = lastFutureResult.get(5000, TimeUnit.MILLISECONDS);
+        } catch (CancellationException e) {
+            return;//ok
+        }
+        fail("Expected exception not thrown");
+    }
+
+    @Then("reset mock responses")
+    public void  resetMockResponses(){
+        responses.clear();
+    }
 }
diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story
index 65334e2d..576fb551 100644
--- a/acceptance/src/test/resources/scenarios/requester_responder.story
+++ b/acceptance/src/test/resources/scenarios/requester_responder.story
@@ -6,6 +6,7 @@ And clear log
 After:
 Outcome: ANY
 Then shutdown MSB
+Then reset mock responses
 
 Scenario: Sends a request to a responder server and waits for response
 
@@ -132,3 +133,31 @@ Then requester gets response in 5000 ms
 And responder requests received count equals 1
 And request forward namespace equals test:jbehave:forwarding
 
+Scenario: Requester sends request for single future response
+
+Given responder server responds with '{"result": "hello jbehave - future"}'
+And responder server listens on namespace test:jbehave
+When requester sends a request for single result to namespace test:jbehave
+And requester blocks waiting for response for 5000 ms
+Then resolved response equals
+|result|
+|hello jbehave - future|
+
+Scenario: Actual response comes after acknowledge
+
+Given next response from responder contains acknowledge with 1 remaining response
+And next response from responder contains body '{"result": "hello jbehave - future"}'
+And responder server responds sequentially on namespace test:jbehave
+When requester sends a request for single result to namespace test:jbehave
+And requester blocks waiting for response for 5000 ms
+Then resolved response equals
+|result|
+|hello jbehave - future|
+
+Scenario: To many responses
+
+Given next response from responder contains acknowledge with 2 remaining response
+And next response from responder contains body '{"result": "hello jbehave - future"}'
+And responder server responds sequentially on namespace test:jbehave
+When requester sends a request for single result to namespace test:jbehave
+Then requester gets exception when tries to obtain result
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
index a32edbc3..9c1763df 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
@@ -43,6 +43,15 @@ public Type getType() {
      */
      Requester createRequester(String namespace, RequestOptions requestOptions, TypeReference payloadTypeReference);
 
+    /**
+     * Creates requester for single response with default response and acknowledgment timeouts
+     *
+     * @param namespace             topic name to send a request to
+     * @param payloadClass  expected payload class of response messages
+     * @return new instance of a {@link Requester} with original message
+     */
+     Requester createRequesterForSingleResponse(String namespace, Class payloadClass);
+
     /**
      * Convenience method that specifies incoming payload type as {@link JsonNode}
      *
@@ -78,6 +87,14 @@ public Type getType() {
         });
     }
 
+    /**
+     * Creates requester that doesn't wait for any responses or acknowledgments
+     *
+     * @param namespace             topic name to send a request to
+     * @return new instance of a {@link Requester} with original message
+     */
+     Requester createRequesterForFireAndForget(String namespace);
+
     /**
      * @param namespace                 topic on a bus for listening on incoming requests
      * @param messageTemplate           template used for creating response messages
diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
index a30d7f21..7a73863d 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
@@ -110,6 +110,19 @@ public Builder withForwardNamespace(String forward) {
             return this;
         }
 
+        /**
+         * Convenience method to prepare Builder with properties equal to {@literal source} properties.
+         * Is useful for cases when almost same RequestOptions except one or two properties are needed.
+         */
+        public Builder from(RequestOptions source) {
+            this.ackTimeout = source.ackTimeout;
+            this.responseTimeout = source.responseTimeout;
+            this.waitForResponses = source.waitForResponses;
+            this.messageTemplate = source.messageTemplate;
+            this.forwardNamespace = source.forwardNamespace;
+            return this;
+        }
+
         public RequestOptions build() {
             return new RequestOptions(ackTimeout, responseTimeout, waitForResponses, messageTemplate, forwardNamespace);
         }
diff --git a/core/src/main/java/io/github/tcdl/msb/api/Requester.java b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
index 0b56f7e0..226f4132 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/Requester.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
@@ -5,6 +5,7 @@
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.Message;
 
+import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
 
 /**
@@ -62,6 +63,43 @@ public interface Requester {
      */
     void publish(Object requestPayload, Message originalMessage);
 
+    /**
+     * Similar to {@link Requester#publish(java.lang.Object)} but expects exactly one response.
+     * CompletableFuture response type adds a lot of flexibility to client implementation.
+     * @return {@link CompletableFuture} that will be completed when first response is received.
+     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
+     * is received.
+     */
+    CompletableFuture request(Object requestPayload);
+
+     /**
+     * Similar to {@link Requester#publish(java.lang.Object, java.lang.String...)} but expects exactly one response.
+     * CompletableFuture response type adds a lot of flexibility to client implementation.
+     * @return {@link CompletableFuture} that will be completed when first response is received.
+     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
+     * is received.
+     */
+    CompletableFuture request(Object requestPayload, String... tags);
+
+     /**
+     * Similar to {@link Requester#publish(java.lang.Object, io.github.tcdl.msb.api.message.Message)}
+     * but expects exactly one response. CompletableFuture response type adds a lot of flexibility to client implementation.
+     * @return {@link CompletableFuture} that will be completed when first response is received.
+     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
+     * is received.
+     */
+    CompletableFuture request(Object requestPayload, Message originalMessage);
+
+    /**
+     * Similar to
+     * {@link io.github.tcdl.msb.api.Requester#publish(java.lang.Object, io.github.tcdl.msb.api.message.Message, java.lang.String...)}
+     * but expects exactly one response. CompletableFuture response type adds a lot of flexibility to client implementation.
+     * @return {@link CompletableFuture} that will be completed when first response is received.
+     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
+     * is received.
+     */
+    CompletableFuture request(Object requestPayload, Message originalMessage, String... tags);
+
     /**
      * Registers a callback to be called when {@link Message} with {@link Acknowledge} part set is received.
      *
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
index df998e91..f21c71fb 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
@@ -123,11 +123,9 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt
         this.timeoutMs = getResponseTimeoutFromConfigs(requestOptions);
         this.currentTimeoutMs = timeoutMs;
 
-        int waitForResponses = requestOptions.getWaitForResponses();
+        this.responsesRemaining = requestOptions.getWaitForResponses();
 
-        this.responsesRemaining = waitForResponses;
-
-        shouldWaitUntilResponseTimeout = (waitForResponses == RequestOptions.WAIT_FOR_RESPONSES_UNTIL_TIMEOUT);
+        this.shouldWaitUntilResponseTimeout = (responsesRemaining == RequestOptions.WAIT_FOR_RESPONSES_UNTIL_TIMEOUT);
 
         this.payloadTypeReference = payloadTypeReference;
 
diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java
index cd0d8929..cfdf9e40 100644
--- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java
+++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java
@@ -43,6 +43,10 @@ public class MsbConfig {
 
     private final int consumerThreadPoolQueueCapacity;
 
+    private final int defaultResponseTimeout;
+
+    private final int defaultAckTimeout;
+
     public MsbConfig(Config loadedConfig) {
         Config config = loadedConfig.getConfig("msbConfig");
 
@@ -65,6 +69,11 @@ public MsbConfig(Config loadedConfig) {
         this.mdcLoggingSplitTagsBy = getOptionalString(mdcLogging, "splitTagsBy").orElse(null);
         this.mdcLoggingKeyMessageTags = getString(mdcLoggingMessageKeys, "messageTags");
         this.mdcLoggingKeyCorrelationId = getString(mdcLoggingMessageKeys, "correlationId");
+
+        Config requestOptionsConfig = config.getConfig("requestOptions");
+        this.defaultResponseTimeout = getInt(requestOptionsConfig, "responseTimeout");
+        this.defaultAckTimeout = getInt(requestOptionsConfig, "ackTimeout");
+
         LOG.debug("Loaded {}", this);
     }
 
@@ -121,6 +130,14 @@ public String getMdcLoggingSplitTagsBy() {
         return mdcLoggingSplitTagsBy;
     }
 
+    public int getDefaultResponseTimeout() {
+        return defaultResponseTimeout;
+    }
+
+    public int getDefaultAckTimeout() {
+        return defaultAckTimeout;
+    }
+
     @Override public String toString() {
         //please keep custom "brokerConfig" when using auto-generation of this method
         return "MsbConfig{" +
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
index ade2afe1..c58d1aa7 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
@@ -10,11 +10,13 @@
 import io.github.tcdl.msb.api.ResponderServer;
 import io.github.tcdl.msb.api.monitor.AggregatorStats;
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
+import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.monitor.aggregator.DefaultChannelMonitorAggregator;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.lang.reflect.Type;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.ThreadFactory;
@@ -40,6 +42,34 @@ public  Requester createRequester(String namespace, RequestOptions request
         return RequesterImpl.create(namespace, requestOptions, msbContext, payloadTypeReference);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public  Requester createRequesterForSingleResponse(String namespace, Class payloadClass) {
+        MsbConfig msbConfig = msbContext.getMsbConfig();
+
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(1)
+                .withResponseTimeout(msbConfig.getDefaultResponseTimeout())
+                .withAckTimeout(msbConfig.getDefaultAckTimeout())
+                .build();
+
+        return RequesterImpl.create(namespace, requestOptions, msbContext, toTypeReference(payloadClass));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public  Requester createRequesterForFireAndForget(String namespace) {
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(0)
+                .build();
+
+        return RequesterImpl.create(namespace, requestOptions, msbContext, null);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -96,4 +126,13 @@ public synchronized void shutdown() {
     DefaultChannelMonitorAggregator createDefaultChannelMonitorAggregator(Callback aggregatorStatsHandler, ScheduledExecutorService scheduledExecutorService) {
         return new DefaultChannelMonitorAggregator(msbContext, scheduledExecutorService, aggregatorStatsHandler);
     }
+
+    private static  TypeReference toTypeReference(Class clazz) {
+        return new TypeReference() {
+            @Override
+            public Type getType() {
+                return clazz;
+            }
+        };
+    }
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
index be1e7fb7..b07b5480 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
@@ -2,18 +2,16 @@
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import io.github.tcdl.msb.ChannelManager;
-import io.github.tcdl.msb.api.Callback;
-import io.github.tcdl.msb.api.MessageContext;
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.RequestOptions;
-import io.github.tcdl.msb.api.Requester;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.collector.Collector;
 import io.github.tcdl.msb.events.EventHandlers;
 import io.github.tcdl.msb.message.MessageFactory;
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.Validate;
 
+import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
 
 /**
@@ -64,7 +62,7 @@ private RequesterImpl(String namespace, RequestOptions requestOptions, MsbContex
      */
     @Override
     public void publish(Object requestPayload) {
-        publish(requestPayload, null, null);
+        publish(requestPayload, null, ArrayUtils.EMPTY_STRING_ARRAY);
     }
 
     /**
@@ -80,7 +78,57 @@ public void publish(Object requestPayload, String... tags) {
      */
     @Override
     public void publish(Object requestPayload, Message originalMessage) {
-        publish(requestPayload, originalMessage, null);
+        publish(requestPayload, originalMessage, ArrayUtils.EMPTY_STRING_ARRAY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CompletableFuture request(Object requestPayload) {
+        return request(requestPayload, null, ArrayUtils.EMPTY_STRING_ARRAY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CompletableFuture request(Object requestPayload, String... tags) {
+        return request(requestPayload, null, tags);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CompletableFuture request(Object requestPayload, Message originalMessage) {
+        return request(requestPayload, originalMessage, ArrayUtils.EMPTY_STRING_ARRAY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CompletableFuture request(Object requestPayload, Message originalMessage, String... tags) {
+        CompletableFuture futureResult = new CompletableFuture<>();
+
+        this.onResponse((response, messageContext) -> futureResult.complete(response))
+                .onAcknowledge((acknowledge, messageContext) -> {
+                    boolean noResponse = !futureResult.isDone() && acknowledge.getResponsesRemaining() < 1;
+                    boolean tooManyResponses = acknowledge.getResponsesRemaining() > 1;
+                    if (noResponse || tooManyResponses) {
+                        futureResult.cancel(true);
+                    }
+                })
+                .onEnd(end -> {
+                    if (!futureResult.isDone()) {
+                        futureResult.cancel(true);
+                    }
+                })
+                .onError((exception, message) -> futureResult.cancel(true));
+
+        publish(requestOptions, requestPayload, originalMessage, tags);
+        return futureResult;
     }
 
     /**
@@ -88,6 +136,10 @@ public void publish(Object requestPayload, Message originalMessage) {
      */
     @Override
     public void publish(Object requestPayload, Message originalMessage, String... tags) {
+        publish(requestOptions, requestPayload, originalMessage, tags);
+    }
+
+    private void publish(RequestOptions requestOptions, Object requestPayload, Message originalMessage, String... tags) {
         MessageTemplate messageTemplate = MessageTemplate.copyOf(requestOptions.getMessageTemplate());
         if (tags != null) {
             for(String tag: tags) {
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java
index 17a7ec66..42715a7a 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java
@@ -19,18 +19,14 @@ public class ResponderImpl implements Responder {
     private static final Logger LOG = LoggerFactory.getLogger(ResponderImpl.class);
 
     private String responderId;
-    private Message originalMessage;
     private ChannelManager channelManager;
     private MessageFactory messageFactory;
     private Message.Builder messageBuilder;
-    private AcknowledgementHandler ackHandler;
 
     public ResponderImpl(MessageTemplate messageTemplate, Message originalMessage, 
             MsbContextImpl msbContext) {
         validateReceivedMessage(originalMessage);
         this.responderId = Utils.generateId();
-        this.originalMessage = originalMessage;
-        this.ackHandler = ackHandler;
         this.channelManager = msbContext.getChannelManager();
         this.messageFactory = msbContext.getMessageFactory();
         this.messageBuilder = messageFactory.createResponseMessageBuilder(messageTemplate, originalMessage);
diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf
index fb3e0b23..48022831 100644
--- a/core/src/main/resources/reference.conf
+++ b/core/src/main/resources/reference.conf
@@ -35,5 +35,10 @@ msbConfig {
       correlationId = "msbCorrelationId"
     }
   }
+
+  requestOptions {
+    responseTimeout = 5000
+    ackTimeout = 5000
+  }
 }
 
diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
index e3392152..bc8fd35d 100644
--- a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
@@ -1,6 +1,7 @@
 package io.github.tcdl.msb.api;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
 
 import org.junit.Test;
 
@@ -44,4 +45,31 @@ public void testForwardNamespace() {
         assertEquals(forwardNamespace, requestOptions.getForwardNamespace());
     }
 
+    @Test
+    public void testBuilderFromExistingRequestOptions() throws Exception {
+
+        MessageTemplate sourceMessageTemplate = new MessageTemplate();
+
+        Integer ackTimeout = 1;
+        Integer responseTimeout = 2;
+        String forwardNamespace = "forward:namespace";
+        int waitForResponses = 3;
+
+        RequestOptions source = new RequestOptions.Builder()
+                .withAckTimeout(ackTimeout)
+                .withResponseTimeout(responseTimeout)
+                .withForwardNamespace(forwardNamespace)
+                .withWaitForResponses(waitForResponses)
+                .withMessageTemplate(sourceMessageTemplate)
+                .build();
+
+        RequestOptions.Builder builder = new RequestOptions.Builder().from(source);
+        RequestOptions result = builder.build();
+
+        assertEquals(ackTimeout, result.getAckTimeout());
+        assertEquals(responseTimeout, result.getResponseTimeout());
+        assertEquals(waitForResponses, result.getWaitForResponses());
+        assertEquals(forwardNamespace, result.getForwardNamespace());
+        assertSame(sourceMessageTemplate, result.getMessageTemplate());
+    }
 }
\ No newline at end of file
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java
index 748d1fa7..7589faf6 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java
@@ -77,4 +77,11 @@ public void getPayloadConverter() {
         PayloadConverter payloadConverter = objectFactory.getPayloadConverter();
         assertNotNull(payloadConverter);
     }
+
+    @Test
+    public void testCreateFireAndForgetRequester() throws Exception {
+        ObjectFactory objectFactory = new ObjectFactoryImpl(TestUtils.createMsbContextBuilder().build());
+        Requester expectedRequester = objectFactory.createRequesterForFireAndForget(NAMESPACE);
+        assertNotNull(expectedRequester);
+    }
 }
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
index 7b513994..c6475832 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
@@ -2,16 +2,12 @@
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.JsonNode;
-import io.github.tcdl.msb.api.Callback;
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.ObjectFactory;
-import io.github.tcdl.msb.api.PayloadConverter;
-import io.github.tcdl.msb.api.RequestOptions;
-import io.github.tcdl.msb.api.Requester;
-import io.github.tcdl.msb.api.ResponderServer;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.monitor.AggregatorStats;
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
 
+import java.lang.reflect.Type;
+
 /**
  * {@link ObjectFactory} implementation that captures all requesters/responders params and callbacks to be
  * used during testing.
@@ -31,6 +27,17 @@ public  Requester createRequester(String namespace, RequestOptions request
         return capture.getRequesterMock();
     }
 
+    @Override
+    public  Requester createRequesterForSingleResponse(String namespace, Class payloadClass) {
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(1)
+                .withResponseTimeout(5000)
+                .withAckTimeout(5000)
+                .build();
+
+        return createRequester(namespace, requestOptions, toTypeReference(payloadClass));
+    }
+
     @Override
     public Requester createRequester(String namespace, RequestOptions requestOptions) {
         RequesterCapture capture = new RequesterCapture<>(namespace, requestOptions, null, null);
@@ -77,13 +84,22 @@ public  ResponderServer createResponderServer(String namespace, MessageTempla
         return capture.getResponderServerMock();
     }
 
-    @Override public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
+    @Override
+    public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) {
         ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, errorHandler, null, payloadClass);
         storage.addCapture(capture);
         return capture.getResponderServerMock();
     }
 
+    @Override
+    public  Requester createRequesterForFireAndForget(String namespace) {
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(0)
+                .build();
+        return createRequester(namespace, requestOptions, (Class)null);
+    }
+
     @Override
     public PayloadConverter getPayloadConverter() {
         return null;
@@ -99,4 +115,13 @@ public void shutdown() {
 
     }
 
+    private static  TypeReference toTypeReference(Class clazz) {
+        return new TypeReference() {
+            @Override
+            public Type getType() {
+                return clazz;
+            }
+        };
+    }
+
 }

From 38cc9fc0bdb80d3194819972339e311181676383 Mon Sep 17 00:00:00 2001
From: alex 
Date: Tue, 17 May 2016 13:53:06 +0300
Subject: [PATCH 124/226] API enhancement. Added bunch of overloaded request()
 methods in Requester. Added two convenience methods in ObjectFactory.

---
 .../src/test/resources/scenarios/requester_responder.story    | 3 ++-
 .../tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java     | 4 ++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story
index 576fb551..01b1d0ab 100644
--- a/acceptance/src/test/resources/scenarios/requester_responder.story
+++ b/acceptance/src/test/resources/scenarios/requester_responder.story
@@ -3,10 +3,11 @@ Before:
 Given MSB configuration with consumer prefetch count 20
 And start MSB
 And clear log
+And reset mock responses
 After:
 Outcome: ANY
 Then shutdown MSB
-Then reset mock responses
+
 
 Scenario: Sends a request to a responder server and waits for response
 
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
index c6475832..9a299cdf 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
@@ -31,8 +31,8 @@ public  Requester createRequester(String namespace, RequestOptions request
     public  Requester createRequesterForSingleResponse(String namespace, Class payloadClass) {
         RequestOptions requestOptions = new RequestOptions.Builder()
                 .withWaitForResponses(1)
-                .withResponseTimeout(5000)
-                .withAckTimeout(5000)
+                .withResponseTimeout(100)
+                .withAckTimeout(100)
                 .build();
 
         return createRequester(namespace, requestOptions, toTypeReference(payloadClass));

From 12b64f794fe613950efbeac0c969356d01b5d2ef Mon Sep 17 00:00:00 2001
From: alex 
Date: Thu, 19 May 2016 12:21:54 +0300
Subject: [PATCH 125/226] API enhancement. Added
 ExecutionOptionsAwareMessageHandler interface and implemented direct
 invocation in Consumer

---
 .../io/github/tcdl/msb/api/Requester.java     |  36 ++--
 .../github/tcdl/msb/collector/Collector.java  |  15 +-
 .../ExecutionOptionsAwareMessageHandler.java  |  16 ++
 .../github/tcdl/msb/impl/RequesterImpl.java   |  14 +-
 .../tcdl/msb/collector/CollectorTest.java     |   1 +
 .../tcdl/msb/impl/RequesterImplTest.java      | 187 +++++++++++++++---
 6 files changed, 213 insertions(+), 56 deletions(-)
 create mode 100644 core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java

diff --git a/core/src/main/java/io/github/tcdl/msb/api/Requester.java b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
index 226f4132..41f9ad4d 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/Requester.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/Requester.java
@@ -64,29 +64,20 @@ public interface Requester {
     void publish(Object requestPayload, Message originalMessage);
 
     /**
-     * Similar to {@link Requester#publish(java.lang.Object)} but expects exactly one response.
-     * CompletableFuture response type adds a lot of flexibility to client implementation.
-     * @return {@link CompletableFuture} that will be completed when first response is received.
-     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
-     * is received.
+     * Overloaded version of
+     * {@link Requester#request(java.lang.Object, io.github.tcdl.msb.api.message.Message, java.lang.String...)}
      */
     CompletableFuture request(Object requestPayload);
 
-     /**
-     * Similar to {@link Requester#publish(java.lang.Object, java.lang.String...)} but expects exactly one response.
-     * CompletableFuture response type adds a lot of flexibility to client implementation.
-     * @return {@link CompletableFuture} that will be completed when first response is received.
-     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
-     * is received.
+    /**
+     * Overloaded version of
+     * {@link Requester#request(java.lang.Object, io.github.tcdl.msb.api.message.Message, java.lang.String...)}
      */
     CompletableFuture request(Object requestPayload, String... tags);
 
-     /**
-     * Similar to {@link Requester#publish(java.lang.Object, io.github.tcdl.msb.api.message.Message)}
-     * but expects exactly one response. CompletableFuture response type adds a lot of flexibility to client implementation.
-     * @return {@link CompletableFuture} that will be completed when first response is received.
-     * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses
-     * is received.
+    /**
+     * Overloaded version of
+     * {@link Requester#request(java.lang.Object, io.github.tcdl.msb.api.message.Message, java.lang.String...)}
      */
     CompletableFuture request(Object requestPayload, Message originalMessage);
 
@@ -94,6 +85,17 @@ public interface Requester {
      * Similar to
      * {@link io.github.tcdl.msb.api.Requester#publish(java.lang.Object, io.github.tcdl.msb.api.message.Message, java.lang.String...)}
      * but expects exactly one response. CompletableFuture response type adds a lot of flexibility to client implementation.
+
+     * All handlers passed to
+     * 
    + *
  • {@link Requester#onAcknowledge(java.util.function.BiConsumer)}
  • + *
  • {@link Requester#onResponse(java.util.function.BiConsumer)}
  • + *
  • {@link Requester#onRawResponse(java.util.function.BiConsumer)}
  • + *
  • {@link Requester#onEnd(io.github.tcdl.msb.api.Callback)}
  • + *
  • {@link Requester#onError(java.util.function.BiConsumer)}
  • + *
+ * are DISCARDED + * * @return {@link CompletableFuture} that will be completed when first response is received. * CompletableFuture will be canceled if timeout occurs or acknowledge with different from 1 remaining responses * is received. diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index f21c71fb..7bf7a9cb 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -36,7 +36,7 @@ /** * {@link Collector} is a component which collects responses and acknowledgements for sent requests. */ -public class Collector implements ConsumedMessagesAwareMessageHandler { +public class Collector implements ConsumedMessagesAwareMessageHandler, ExecutionOptionsAwareMessageHandler { private static final Logger LOG = LoggerFactory.getLogger(Collector.class); @@ -101,8 +101,15 @@ public class Collector implements ConsumedMessagesAwareMessageHandler { */ private volatile boolean isOnEndInvoked = false; + private final boolean directlyInvokable; + public Collector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl msbContext, EventHandlers eventHandlers, TypeReference payloadTypeReference) { + this(topic, requestMessage, requestOptions, msbContext, eventHandlers, payloadTypeReference, false); + } + + public Collector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl msbContext, EventHandlers eventHandlers, + TypeReference payloadTypeReference, boolean directlyInvokableCallbacks) { this.requestMessage = requestMessage; this.clock = msbContext.getClock(); @@ -134,6 +141,7 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt onAcknowledge = Optional.ofNullable(eventHandlers.onAcknowledge()); onEnd = Optional.ofNullable(eventHandlers.onEnd()); onError = Optional.ofNullable(eventHandlers.onError()); + this.directlyInvokable = directlyInvokableCallbacks; } @Override @@ -407,4 +415,9 @@ List getPayloadMessages() { Message getRequestMessage() { return requestMessage; } + + @Override + public boolean isDirectlyInvokable(){ + return directlyInvokable; + } } diff --git a/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java b/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java new file mode 100644 index 00000000..09ef6b08 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java @@ -0,0 +1,16 @@ +package io.github.tcdl.msb.collector; + +import io.github.tcdl.msb.MessageHandler; + +/** + * Created by Alexandr Zolotov + * 19.05.16 + */ +public interface ExecutionOptionsAwareMessageHandler extends MessageHandler { + + /** + * Indicates whether handler should be executed by main message handling thread (true is so) or by thread from + * consumer thread pool. + */ + boolean isDirectlyInvokable(); +} \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java index b07b5480..618b9f2f 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java @@ -110,6 +110,8 @@ public CompletableFuture request(Object requestPayload, Message originalMessa */ @Override public CompletableFuture request(Object requestPayload, Message originalMessage, String... tags) { + this.eventHandlers = new EventHandlers<>(); //discard all previously set handlers + CompletableFuture futureResult = new CompletableFuture<>(); this.onResponse((response, messageContext) -> futureResult.complete(response)) @@ -127,7 +129,7 @@ public CompletableFuture request(Object requestPayload, Message originalMessa }) .onError((exception, message) -> futureResult.cancel(true)); - publish(requestOptions, requestPayload, originalMessage, tags); + publish(true, requestOptions, requestPayload, originalMessage, tags); return futureResult; } @@ -136,10 +138,10 @@ public CompletableFuture request(Object requestPayload, Message originalMessa */ @Override public void publish(Object requestPayload, Message originalMessage, String... tags) { - publish(requestOptions, requestPayload, originalMessage, tags); + publish(false, requestOptions, requestPayload, originalMessage, tags); } - private void publish(RequestOptions requestOptions, Object requestPayload, Message originalMessage, String... tags) { + private void publish(boolean invokeHandlersDirectly, RequestOptions requestOptions, Object requestPayload, Message originalMessage, String... tags) { MessageTemplate messageTemplate = MessageTemplate.copyOf(requestOptions.getMessageTemplate()); if (tags != null) { for(String tag: tags) { @@ -160,7 +162,7 @@ private void publish(RequestOptions requestOptions, Object requestPayload, Messa if (isWaitForAckMs() || isWaitForResponses()) { String topic = message.getTopics().getResponse(); - Collector collector = createCollector(topic, message, requestOptions, context, eventHandlers); + Collector collector = createCollector(topic, message, requestOptions, context, eventHandlers, invokeHandlersDirectly); collector.listenForResponses(); getChannelManager().findOrCreateProducer(message.getTopics().getTo()) @@ -229,7 +231,7 @@ private ChannelManager getChannelManager() { return context.getChannelManager(); } - Collector createCollector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl context, EventHandlers eventHandlers) { - return new Collector<>(topic, requestMessage, requestOptions, context, eventHandlers, payloadTypeReference); + Collector createCollector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl context, EventHandlers eventHandlers, boolean invokeHandlersDirectly) { + return new Collector<>(topic, requestMessage, requestOptions, context, eventHandlers, payloadTypeReference, invokeHandlersDirectly); } } diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java index 6eefc704..5ee34d7e 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java @@ -186,6 +186,7 @@ MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandle assertFalse(collector.getAckMessages().contains(responseMessage)); } + @Test public void testHandleResponseConversionFailed() { String bodyText = "some body"; Message responseMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index b29f40ba..9232f919 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -9,6 +9,7 @@ import io.github.tcdl.msb.api.MessageTemplate; import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Requester; +import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.collector.Collector; @@ -20,18 +21,14 @@ import org.mockito.runners.MockitoJUnitRunner; import java.time.Clock; +import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; +import static io.github.tcdl.msb.support.TestUtils.createPayloadWithTextBody; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertArrayEquals; -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.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -62,7 +59,7 @@ public class RequesterImplTest { @Test public void testPublishNoWaitForResponses() throws Exception { - RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null, null, null, null); publishByAllMethods(requester); @@ -72,7 +69,7 @@ public void testPublishNoWaitForResponses() throws Exception { @Test public void testPublishWaitForResponses() throws Exception { - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); publishByAllMethods(requester); @@ -90,7 +87,7 @@ private void publishByAllMethods(RequesterImpl requester) { @Test public void testPublishWaitForResponsesAck() throws Exception { - RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, null, null, arg -> fail()); + RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, null, null, null, arg -> fail()); requester.publish(TestUtils.createSimpleRequestPayload()); @@ -103,7 +100,7 @@ public void testPublishWaitForResponsesAck() throws Exception { public void testPublishHandleErrorResponse() throws Exception { RuntimeException ex = new RuntimeException(); BiConsumer errorHandlerMock = mock(BiConsumer.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, (p, c) -> { throw ex; }, errorHandlerMock, arg -> fail()); + RequesterImpl requester = initRequesterForResponsesWith(1, 1000, 800, (p, c) -> { throw ex; }, null, errorHandlerMock, arg -> fail()); requester.publish(TestUtils.createSimpleRequestPayload()); Message responseMessage = TestUtils.createMsbRequestMessage("some:topic", "body text"); @@ -114,9 +111,9 @@ public void testPublishHandleErrorResponse() throws Exception { @Test public void testProducerPublishWithPayload() throws Exception { String bodyText = "Body text"; - RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(0, 0, 0, null, null, null, null); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); - RestPayload payload = TestUtils.createPayloadWithTextBody(bodyText); + RestPayload payload = createPayloadWithTextBody(bodyText); requester.publish(payload); @@ -128,7 +125,7 @@ public void testProducerPublishWithPayload() throws Exception { @SuppressWarnings("unchecked") public void testAcknowledgeEventHandlerIsAdded() throws Exception { BiConsumer onAckMock = mock(BiConsumer.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); requester.onAcknowledge(onAckMock); @@ -143,7 +140,7 @@ public void testAcknowledgeEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testResponseEventHandlerIsAdded() throws Exception { BiConsumer onResponseMock = mock(BiConsumer.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); requester.onResponse(onResponseMock); @@ -158,7 +155,7 @@ public void testResponseEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testRawResponseEventHandlerIsAdded() throws Exception { BiConsumer onRawResponseMock = mock(BiConsumer.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); requester.onRawResponse(onRawResponseMock); @@ -173,7 +170,7 @@ public void testRawResponseEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testEndEventHandlerIsAdded() throws Exception { Callback onEndMock = mock(Callback.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0,null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); requester.onEnd(onEndMock); @@ -188,7 +185,7 @@ public void testEndEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testErrorEventHandlerIsAdded() throws Exception { BiConsumer onErrorMock = mock(BiConsumer.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0,null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); requester.onError(onErrorMock); @@ -203,7 +200,7 @@ public void testErrorEventHandlerIsAdded() throws Exception { @SuppressWarnings("unchecked") public void testNoEventHandlerAdded() throws Exception { Callback onEndMock = mock(Callback.class); - RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null); + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); assertThat(requester.eventHandlers.onAcknowledge(), not(onEndMock)); assertThat(requester.eventHandlers.onResponse(), not(onEndMock)); @@ -212,6 +209,128 @@ public void testNoEventHandlerAdded() throws Exception { assertThat(requester.eventHandlers.onError(), not(onEndMock)); } + @SuppressWarnings("unchecked") + @Test + public void testRequest_customHandlersAreDiscarded() throws Exception { + + BiConsumer customOnResponseHandler = mock(BiConsumer.class); + BiConsumer customOnRawResponseHandler = mock(BiConsumer.class); + BiConsumer customOnAcknowledgeHandler = mock(BiConsumer.class); + BiConsumer customOnErrorHandler = mock(BiConsumer.class); + Callback customOnEndHandler = mock(Callback.class); + + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, customOnResponseHandler, customOnAcknowledgeHandler, customOnErrorHandler, customOnEndHandler); + + requester.onRawResponse(customOnRawResponseHandler); + + requester.request(TestUtils.createSimpleRequestPayload()); + + assertThat(requester.eventHandlers.onAcknowledge(), not(customOnAcknowledgeHandler)); + assertThat(requester.eventHandlers.onResponse(), not(customOnResponseHandler)); + assertThat(requester.eventHandlers.onRawResponse(), not(customOnRawResponseHandler)); + assertThat(requester.eventHandlers.onEnd(), not(customOnEndHandler)); + assertThat(requester.eventHandlers.onError(), not(customOnErrorHandler)); + } + + @Test + public void testRequest_responseHandlerCompletesFuture() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + assertFalse(futureResult.isDone()); + + RestPayload mockResponsePayload = mock(RestPayload.class); + MessageContext mockMessageContext = mock(MessageContext.class); + + requester.eventHandlers.onResponse().accept(mockResponsePayload, mockMessageContext); + assertTrue(futureResult.isDone()); + assertEquals(mockResponsePayload, futureResult.get()); + } + + @Test + public void testRequest_rawResponseHandlerDoesNotCompleteFuture() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + assertFalse(futureResult.isDone()); + + Message responseMessage = TestUtils.createSimpleResponseMessage("anyNamespace"); + MessageContext mockMessageContext = mock(MessageContext.class); + + requester.eventHandlers.onRawResponse().accept(responseMessage, mockMessageContext); + assertFalse(futureResult.isDone()); + } + + @Test + public void testRequest_errorHandlerCancelsFuture() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + assertFalse(futureResult.isDone()); + + Message responseMessage = TestUtils.createSimpleResponseMessage("anyNamespace"); + Exception e = new Exception("some message"); + + requester.eventHandlers.onError().accept(e, responseMessage); + assertTrue(futureResult.isCancelled()); + } + + @Test + public void testRequest_endHandlerCancelsNotCompletedFuture() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + assertFalse(futureResult.isDone()); + + requester.eventHandlers.onEnd().call(null); + assertTrue(futureResult.isCancelled()); + } + + @Test + public void testRequest_endHandlerDoesNothingWithCompletedFuture() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + RestPayload mockResponsePayload = mock(RestPayload.class); + MessageContext mockMessageContext = mock(MessageContext.class); + + requester.eventHandlers.onResponse().accept(mockResponsePayload, mockMessageContext); + requester.eventHandlers.onEnd().call(null); + assertFalse(futureResult.isCancelled()); + } + + @Test + public void testRequest_acknowledgeHandlerCancelsFutureOnNoResponses() throws Exception { + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + MessageContext mockMessageContext = mock(MessageContext.class); + Acknowledge acknowledge = new Acknowledge.Builder() + .withResponderId("responderId") + .withResponsesRemaining(0) + .withTimeoutMs(0) + .build(); + + requester.eventHandlers.onAcknowledge().accept(acknowledge, mockMessageContext); + assertTrue(futureResult.isCancelled()); + } + + @Test + public void testRequest_acknowledgeHandlerCancelsFutureOnTooManyResponses() throws Exception{ + RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); + CompletableFuture futureResult = requester.request(TestUtils.createSimpleRequestPayload()); + + MessageContext mockMessageContext = mock(MessageContext.class); + Acknowledge acknowledge = new Acknowledge.Builder() + .withResponderId("responderId") + .withResponsesRemaining(2) + .withTimeoutMs(0) + .build(); + + requester.eventHandlers.onAcknowledge().accept(acknowledge, mockMessageContext); + assertTrue(futureResult.isCancelled()); + } + @Test public void testRequestMessage() throws Exception { ChannelManager channelManagerMock = mock(ChannelManager.class); @@ -225,7 +344,7 @@ public void testRequestMessage() throws Exception { .build(); RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); - Requester requester = RequesterImpl.create(NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference() {}); + Requester requester = RequesterImpl.create(NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference()); requester.publish(requestPayload); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -254,7 +373,7 @@ public void testRequestMessageWithTags() throws Exception { RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); RequestOptions requestOptions = TestUtils.createSimpleRequestOptionsWithTags(tag); - Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() {}); + Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference()); requester.publish(requestPayload, dynamicTag1, dynamicTag2, nullTag); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -280,7 +399,8 @@ public void testRequestMessageWithForward() throws Exception { .Builder() .withForwardNamespace(forwardNamespace).build(); - Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() {}); + Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() { + }); requester.publish(requestPayload); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -288,10 +408,10 @@ public void testRequestMessageWithForward() throws Exception { assertEquals(forwardNamespace, requestMessage.getTopics().getForward()); } - private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout, Integer ackTimeout, - BiConsumer onResponse, - BiConsumer onError, - Callback endHandler) throws Exception { + private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout, Integer ackTimeout, + BiConsumer onResponse, BiConsumer onAcknowledge, + BiConsumer onError, + Callback endHandler) throws Exception { MessageTemplate messageTemplateMock = mock(MessageTemplate.class); @@ -308,17 +428,20 @@ private RequesterImpl initRequesterForResponsesWith(Integer numberO .withChannelManager(channelManagerMock) .build(); - RequesterImpl requester = spy(RequesterImpl.create(NAMESPACE, requestOptionsMock, msbContext, new TypeReference() {})); + RequesterImpl requester = spy(RequesterImpl.create(NAMESPACE, requestOptionsMock, msbContext, new TypeReference() { + })); requester.onResponse(onResponse) - .onError(onError) - .onEnd(endHandler); + .onError(onError) + .onAcknowledge(onAcknowledge) + .onEnd(endHandler); collectorMock = spy(new Collector<>(NAMESPACE, TestUtils.createMsbRequestMessageNoPayload(NAMESPACE), requestOptionsMock, msbContext, requester.eventHandlers, - new TypeReference() {})); + new TypeReference() { + })); doReturn(collectorMock) .when(requester) - .createCollector(anyString(), any(Message.class), any(RequestOptions.class), any(MsbContextImpl.class), any()); + .createCollector(anyString(), any(Message.class), any(RequestOptions.class), any(MsbContextImpl.class), any(), anyBoolean()); return requester; } From 055dacbc64cd261b84eb38865165d9010c2825c0 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 19 May 2016 12:22:58 +0300 Subject: [PATCH 126/226] API enhancement. Added ExecutionOptionsAwareMessageHandler interface and implemented direct invocation in Consumer --- .../test/java/io/github/tcdl/msb/impl/RequesterImplTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index 9232f919..13c89bbc 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -344,7 +344,7 @@ public void testRequestMessage() throws Exception { .build(); RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); - Requester requester = RequesterImpl.create(NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference()); + Requester requester = RequesterImpl.create(NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference(){}); requester.publish(requestPayload); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -373,7 +373,7 @@ public void testRequestMessageWithTags() throws Exception { RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); RequestOptions requestOptions = TestUtils.createSimpleRequestOptionsWithTags(tag); - Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference()); + Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference(){}); requester.publish(requestPayload, dynamicTag1, dynamicTag2, nullTag); verify(producerMock).publish(messageArgumentCaptor.capture()); From 9f7b37f354b995460e3cda5287c9b2e227cb6245 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 20 May 2016 13:56:27 +0300 Subject: [PATCH 127/226] MsbThreadContext#setMessageContex() made public --- .../src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java b/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java index c1d5546b..4246a6b9 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java @@ -15,7 +15,7 @@ public static MessageContext getMessageContext() { return messageContext.get(); } - static void setMessageContext(MessageContext messageContext) { + public static void setMessageContext(MessageContext messageContext) { MsbThreadContext.messageContext.set(messageContext); } From d8831d9f863d12d72cab36f9137b017c7ed1a2cd Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 20 May 2016 15:46:12 +0300 Subject: [PATCH 128/226] Added overloaded version of ObjectFactory.createRequesterForSingleResponse --- .../java/io/github/tcdl/msb/api/ObjectFactory.java | 10 +++++++++- .../java/io/github/tcdl/msb/api/RequestOptions.java | 2 +- .../java/io/github/tcdl/msb/config/MsbConfig.java | 7 ------- .../io/github/tcdl/msb/impl/ObjectFactoryImpl.java | 12 +++++++++--- core/src/main/resources/reference.conf | 1 - .../msb/mock/objectfactory/TestMsbObjectFactory.java | 9 +++++++-- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java index 9c1763df..1a1a6ec9 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java +++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java @@ -43,14 +43,22 @@ public Type getType() { */ Requester createRequester(String namespace, RequestOptions requestOptions, TypeReference payloadTypeReference); + /** + * Same as + * {@link io.github.tcdl.msb.api.ObjectFactory#createRequesterForSingleResponse(java.lang.String, java.lang.Class, int)} + * with default timeout + */ + Requester createRequesterForSingleResponse(String namespace, Class payloadClass); + /** * Creates requester for single response with default response and acknowledgment timeouts * * @param namespace topic name to send a request to * @param payloadClass expected payload class of response messages + * @param timeout response timeout (in milliseconds) * @return new instance of a {@link Requester} with original message */ - Requester createRequesterForSingleResponse(String namespace, Class payloadClass); + Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout); /** * Convenience method that specifies incoming payload type as {@link JsonNode} diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java index 7a73863d..332e0d3f 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java +++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java @@ -8,7 +8,7 @@ public class RequestOptions { public static final int WAIT_FOR_RESPONSES_UNTIL_TIMEOUT = -1; /** - * Min time (in milliseconds) to wait for acknowledgements. + * Max time (in milliseconds) to wait for acknowledgements. */ private final Integer ackTimeout; diff --git a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java index cfdf9e40..12dccdbc 100644 --- a/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java +++ b/core/src/main/java/io/github/tcdl/msb/config/MsbConfig.java @@ -45,8 +45,6 @@ public class MsbConfig { private final int defaultResponseTimeout; - private final int defaultAckTimeout; - public MsbConfig(Config loadedConfig) { Config config = loadedConfig.getConfig("msbConfig"); @@ -72,7 +70,6 @@ public MsbConfig(Config loadedConfig) { Config requestOptionsConfig = config.getConfig("requestOptions"); this.defaultResponseTimeout = getInt(requestOptionsConfig, "responseTimeout"); - this.defaultAckTimeout = getInt(requestOptionsConfig, "ackTimeout"); LOG.debug("Loaded {}", this); } @@ -134,10 +131,6 @@ public int getDefaultResponseTimeout() { return defaultResponseTimeout; } - public int getDefaultAckTimeout() { - return defaultAckTimeout; - } - @Override public String toString() { //please keep custom "brokerConfig" when using auto-generation of this method return "MsbConfig{" + diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java index c58d1aa7..ce7fca67 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java @@ -48,13 +48,19 @@ public Requester createRequester(String namespace, RequestOptions request @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass) { MsbConfig msbConfig = msbContext.getMsbConfig(); + return createRequesterForSingleResponse(namespace, payloadClass, msbConfig.getDefaultResponseTimeout()); + } + /** + * {@inheritDoc} + */ + @Override + public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout) { RequestOptions requestOptions = new RequestOptions.Builder() .withWaitForResponses(1) - .withResponseTimeout(msbConfig.getDefaultResponseTimeout()) - .withAckTimeout(msbConfig.getDefaultAckTimeout()) + .withResponseTimeout(timeout) + .withAckTimeout(0) .build(); - return RequesterImpl.create(namespace, requestOptions, msbContext, toTypeReference(payloadClass)); } diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 48022831..9513d8e8 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -38,7 +38,6 @@ msbConfig { requestOptions { responseTimeout = 5000 - ackTimeout = 5000 } } diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java index 9a299cdf..45f1691e 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java @@ -29,10 +29,15 @@ public Requester createRequester(String namespace, RequestOptions request @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass) { + return createRequesterForSingleResponse(namespace, payloadClass, 100); + } + + @Override + public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout) { RequestOptions requestOptions = new RequestOptions.Builder() .withWaitForResponses(1) - .withResponseTimeout(100) - .withAckTimeout(100) + .withResponseTimeout(timeout) + .withAckTimeout(0) .build(); return createRequester(namespace, requestOptions, toTypeReference(payloadClass)); From 05079cf6753f0635f2736b07f12120b3f2039046 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 23 May 2016 16:19:37 +0300 Subject: [PATCH 129/226] Added DirectInvocationCapableInvoker to guarantee handling of CompletableFuture even if consumer thread is blocked --- .../bdd/steps/ConfigurationSteps.java | 3 +- .../scenarios/blocking_requester.story | 38 +++++++++ .../scenarios/requester_responder.story | 29 ------- .../tcdl/msb/api/MsbContextBuilder.java | 12 +-- .../DirectInvocationCapableInvoker.java | 49 +++++++++++ .../DirectInvocationCapableInvokerTest.java | 82 +++++++++++++++++++ 6 files changed, 178 insertions(+), 35 deletions(-) create mode 100644 acceptance/src/test/resources/scenarios/blocking_requester.story create mode 100644 core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java create mode 100644 core/src/test/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvokerTest.java diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java index 44eb5911..f130166e 100644 --- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java +++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java @@ -17,7 +17,8 @@ public class ConfigurationSteps extends MsbSteps { private String TIME_THREAD_POOL_SIZE = MSB_CONFIG_ROOT + ".timerThreadPoolSize"; private String MSB_BROKER_CONFIG_ROOT = "msbConfig.brokerConfig"; - private String MSB_BROKER_CONSUMER_THREAD_POOL_SIZE = MSB_BROKER_CONFIG_ROOT + ".consumerThreadPoolSize"; + private String MSB_THREADING_CONFIG_ROOT = "msbConfig.threadingConfig"; + private String MSB_BROKER_CONSUMER_THREAD_POOL_SIZE = MSB_THREADING_CONFIG_ROOT + ".consumerThreadPoolSize"; private String MSB_BROKER_CONSUMER_THREAD_POOL_QUEUE_CAPACITY = MSB_BROKER_CONFIG_ROOT + ".consumerThreadPoolQueueCapacity"; private String MSB_BROKER_CONSUMER_THREAD_POOL_PREFETCH_COUNT = MSB_BROKER_CONFIG_ROOT + ".prefetchCount"; diff --git a/acceptance/src/test/resources/scenarios/blocking_requester.story b/acceptance/src/test/resources/scenarios/blocking_requester.story new file mode 100644 index 00000000..a2bf434b --- /dev/null +++ b/acceptance/src/test/resources/scenarios/blocking_requester.story @@ -0,0 +1,38 @@ +Lifecycle: +Before: +Given MSB configuration with consumer thread pool size 1 +And start MSB +And clear log +And reset mock responses +After: +Outcome: ANY +Then shutdown MSB + +Scenario: Requester sends request for single future response + +Given responder server responds with '{"result": "hello jbehave - future"}' +And responder server listens on namespace test:jbehave +When requester sends a request for single result to namespace test:jbehave +And requester blocks waiting for response for 5000 ms +Then resolved response equals +|result| +|hello jbehave - future| + +Scenario: Actual response comes after acknowledge + +Given next response from responder contains acknowledge with 1 remaining response +And next response from responder contains body '{"result": "hello jbehave - future"}' +And responder server responds sequentially on namespace test:jbehave +When requester sends a request for single result to namespace test:jbehave +And requester blocks waiting for response for 5000 ms +Then resolved response equals +|result| +|hello jbehave - future| + +Scenario: To many responses + +Given next response from responder contains acknowledge with 2 remaining response +And next response from responder contains body '{"result": "hello jbehave - future"}' +And responder server responds sequentially on namespace test:jbehave +When requester sends a request for single result to namespace test:jbehave +Then requester gets exception when tries to obtain result \ No newline at end of file diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story index 01b1d0ab..8554c24a 100644 --- a/acceptance/src/test/resources/scenarios/requester_responder.story +++ b/acceptance/src/test/resources/scenarios/requester_responder.story @@ -133,32 +133,3 @@ When requester sends a request Then requester gets response in 5000 ms And responder requests received count equals 1 And request forward namespace equals test:jbehave:forwarding - -Scenario: Requester sends request for single future response - -Given responder server responds with '{"result": "hello jbehave - future"}' -And responder server listens on namespace test:jbehave -When requester sends a request for single result to namespace test:jbehave -And requester blocks waiting for response for 5000 ms -Then resolved response equals -|result| -|hello jbehave - future| - -Scenario: Actual response comes after acknowledge - -Given next response from responder contains acknowledge with 1 remaining response -And next response from responder contains body '{"result": "hello jbehave - future"}' -And responder server responds sequentially on namespace test:jbehave -When requester sends a request for single result to namespace test:jbehave -And requester blocks waiting for response for 5000 ms -Then resolved response equals -|result| -|hello jbehave - future| - -Scenario: To many responses - -Given next response from responder contains acknowledge with 2 remaining response -And next response from responder contains body '{"result": "hello jbehave - future"}' -And responder server responds sequentially on namespace test:jbehave -When requester sends a request for single result to namespace test:jbehave -Then requester gets exception when tries to obtain result diff --git a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java index 7f02f521..8db30cff 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java +++ b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java @@ -152,18 +152,20 @@ public void run() { private MessageHandlerInvoker createMessageHandlerInvoker(AdapterFactory adapterFactory, MsbConfig msbConfig) { ConsumerExecutorFactory consumerExecutorFactory = new ConsumerExecutorFactoryImpl(); + MessageHandlerInvoker consumerMessageHandlerInvoker; if (adapterFactory.isUseMsbThreadingModel()) { - if(messageGroupStrategy == null) { - return new ThreadPoolMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(), - consumerExecutorFactory); + if (messageGroupStrategy == null) { + consumerMessageHandlerInvoker = new ThreadPoolMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(), + consumerExecutorFactory); } else { - return new GroupedExecutorBasedMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(), + consumerMessageHandlerInvoker = new GroupedExecutorBasedMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(), consumerExecutorFactory, messageGroupStrategy); } } else { - return new DirectMessageHandlerInvoker(); + consumerMessageHandlerInvoker = new DirectMessageHandlerInvoker(); } + return new DirectInvocationCapableInvoker(consumerMessageHandlerInvoker); } /** diff --git a/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java b/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java new file mode 100644 index 00000000..93da4ec7 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java @@ -0,0 +1,49 @@ +package io.github.tcdl.msb.threading; + +import io.github.tcdl.msb.MessageHandler; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; +import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.collector.ExecutionOptionsAwareMessageHandler; +import org.apache.commons.lang3.Validate; + +/** + * Created by Alexandr Zolotov + * 23.05.16 + */ +public class DirectInvocationCapableInvoker implements MessageHandlerInvoker { + + private final MessageHandlerInvoker clientMessageHandlerInvoker; + private final DirectMessageHandlerInvoker directMessageHandlerInvoker; + + /** + * Creates composite delegate that is guarantied to have an instance of {@link DirectMessageHandlerInvoker} in its disposal. + * There is no need to instantiate it in client code. It is intended to be used only internally by the library. + */ + public DirectInvocationCapableInvoker(MessageHandlerInvoker clientMessageHandlerInvoker) { + Validate.notNull(clientMessageHandlerInvoker); + this.clientMessageHandlerInvoker = clientMessageHandlerInvoker; + if (clientMessageHandlerInvoker instanceof DirectMessageHandlerInvoker) { + this.directMessageHandlerInvoker = (DirectMessageHandlerInvoker) clientMessageHandlerInvoker; + } else { + this.directMessageHandlerInvoker = new DirectMessageHandlerInvoker(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) { + if (messageHandler instanceof ExecutionOptionsAwareMessageHandler && ((ExecutionOptionsAwareMessageHandler) messageHandler).isDirectlyInvokable()) { + directMessageHandlerInvoker.execute(messageHandler, message, acknowledgeHandler); + } else { + clientMessageHandlerInvoker.execute(messageHandler, message, acknowledgeHandler); + } + } + + @Override + public void shutdown() { + clientMessageHandlerInvoker.shutdown(); + directMessageHandlerInvoker.shutdown(); + } +} \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvokerTest.java b/core/src/test/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvokerTest.java new file mode 100644 index 00000000..8ca6a803 --- /dev/null +++ b/core/src/test/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvokerTest.java @@ -0,0 +1,82 @@ +package io.github.tcdl.msb.threading; + +import io.github.tcdl.msb.MessageHandler; +import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; +import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.collector.ExecutionOptionsAwareMessageHandler; +import io.github.tcdl.msb.support.TestUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class DirectInvocationCapableInvokerTest { + + DirectInvocationCapableInvoker instance; + final String namespace = "some:namespace"; + + @Mock + AcknowledgementHandlerInternal ackHandler; + + @Test + public void execute_shouldUseInternalDirectInvoker() throws Exception { + MessageHandlerInvoker clientMessageHandlerInvoker = mock(MessageHandlerInvoker.class); + instance = new DirectInvocationCapableInvoker(clientMessageHandlerInvoker); + + ExecutionOptionsAwareMessageHandler messageHandler = mock(ExecutionOptionsAwareMessageHandler.class); + when(messageHandler.isDirectlyInvokable()).thenReturn(true); + + Message message = TestUtils.createSimpleRequestMessage(namespace); + instance.execute(messageHandler, message, ackHandler); + + verify(messageHandler).handleMessage(eq(message), eq(ackHandler)); + verify(clientMessageHandlerInvoker, never()).execute(any(MessageHandler.class), any(Message.class), any(AcknowledgementHandlerInternal.class)); + } + + @Test + public void execute_shouldUseClientInvoker_whenNoDirectInvocationNeeded() throws Exception { + + MessageHandlerInvoker clientMessageHandlerInvoker = mock(MessageHandlerInvoker.class); + instance = new DirectInvocationCapableInvoker(clientMessageHandlerInvoker); + + ExecutionOptionsAwareMessageHandler messageHandler = mock(ExecutionOptionsAwareMessageHandler.class); + when(messageHandler.isDirectlyInvokable()).thenReturn(false); + + Message message = TestUtils.createSimpleRequestMessage(namespace); + instance.execute(messageHandler, message, ackHandler); + + verify(clientMessageHandlerInvoker).execute(eq(messageHandler), eq(message), eq(ackHandler)); + } + + @Test + public void execute_shouldUseClientInvoker_whenHandlerIsNotDirectlyInvokable() throws Exception { + MessageHandlerInvoker clientMessageHandlerInvoker = mock(MessageHandlerInvoker.class); + instance = new DirectInvocationCapableInvoker(clientMessageHandlerInvoker); + + MessageHandler messageHandler = mock(MessageHandler.class); + + Message message = TestUtils.createSimpleRequestMessage(namespace); + instance.execute(messageHandler, message, ackHandler); + + verify(clientMessageHandlerInvoker).execute(eq(messageHandler), eq(message), eq(ackHandler)); + } + + @Test + public void execute_shouldUseClientDirectInvoker() throws Exception { + DirectMessageHandlerInvoker clientMessageHandlerInvoker = mock(DirectMessageHandlerInvoker.class); + instance = new DirectInvocationCapableInvoker(clientMessageHandlerInvoker); + + ExecutionOptionsAwareMessageHandler messageHandler = mock(ExecutionOptionsAwareMessageHandler.class); + when(messageHandler.isDirectlyInvokable()).thenReturn(true); + + Message message = TestUtils.createSimpleRequestMessage(namespace); + instance.execute(messageHandler, message, ackHandler); + + verify(clientMessageHandlerInvoker).execute(eq(messageHandler), eq(message), eq(ackHandler)); + } +} \ No newline at end of file From c3abbb1b3574e52386db5df07f519d34f9f8799b Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 23 May 2016 16:53:16 +0300 Subject: [PATCH 130/226] Javadoc typo fix --- .../tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java b/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java index 09ef6b08..ea122305 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java @@ -9,7 +9,7 @@ public interface ExecutionOptionsAwareMessageHandler extends MessageHandler { /** - * Indicates whether handler should be executed by main message handling thread (true is so) or by thread from + * Indicates whether handler should be executed by main message handling thread (true if so) or by thread from * consumer thread pool. */ boolean isDirectlyInvokable(); From 6312fe45fb3750a17ced8e8e4b956b9d7a1fcdde Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 23 May 2016 17:58:47 +0300 Subject: [PATCH 131/226] Added couple more overloaded methods to ObjectFactory --- .../io/github/tcdl/msb/api/ObjectFactory.java | 28 +++++++++++++++---- .../tcdl/msb/impl/ObjectFactoryImpl.java | 14 ++++++++++ .../objectfactory/TestMsbObjectFactory.java | 11 ++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java index 1a1a6ec9..edaf9f06 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java +++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java @@ -50,15 +50,23 @@ public Type getType() { */ Requester createRequesterForSingleResponse(String namespace, Class payloadClass); + /** + * Same as + * {@link io.github.tcdl.msb.api.ObjectFactory#createRequesterForSingleResponse(java.lang.String, io.github.tcdl.msb.api.MessageTemplate, java.lang.Class, int)} + * with default messageTemplate + */ + Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout); + /** * Creates requester for single response with default response and acknowledgment timeouts * - * @param namespace topic name to send a request to - * @param payloadClass expected payload class of response messages - * @param timeout response timeout (in milliseconds) + * @param namespace topic name to send a request to + * @param payloadClass expected payload class of response messages + * @param messageTemplate {@link MessageTemplate} to be used + * @param timeout response timeout (in milliseconds) * @return new instance of a {@link Requester} with original message */ - Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout); + Requester createRequesterForSingleResponse(String namespace, MessageTemplate messageTemplate, Class payloadClass, int timeout); /** * Convenience method that specifies incoming payload type as {@link JsonNode} @@ -95,13 +103,21 @@ public Type getType() { }); } + /** + * Same as + * {@link ObjectFactory#createRequesterForFireAndForget(java.lang.String, io.github.tcdl.msb.api.MessageTemplate)} + * with default messageTemplate + */ + Requester createRequesterForFireAndForget(String namespace); + /** * Creates requester that doesn't wait for any responses or acknowledgments * - * @param namespace topic name to send a request to + * @param namespace topic name to send a request to + * @param messageTemplate {@link MessageTemplate} to be used * @return new instance of a {@link Requester} with original message */ - Requester createRequesterForFireAndForget(String namespace); + Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate); /** * @param namespace topic on a bus for listening on incoming requests diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java index ce7fca67..85df167e 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java @@ -56,7 +56,16 @@ public Requester createRequesterForSingleResponse(String namespace, Class */ @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout) { + return createRequesterForSingleResponse(namespace, null, payloadClass, timeout); + } + + /** + * {@inheritDoc} + */ + @Override + public Requester createRequesterForSingleResponse(String namespace, MessageTemplate messageTemplate, Class payloadClass, int timeout) { RequestOptions requestOptions = new RequestOptions.Builder() + .withMessageTemplate(messageTemplate) .withWaitForResponses(1) .withResponseTimeout(timeout) .withAckTimeout(0) @@ -69,7 +78,12 @@ public Requester createRequesterForSingleResponse(String namespace, Class */ @Override public Requester createRequesterForFireAndForget(String namespace) { + return createRequesterForFireAndForget(namespace, null); + } + + public Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate){ RequestOptions requestOptions = new RequestOptions.Builder() + .withMessageTemplate(messageTemplate) .withWaitForResponses(0) .build(); diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java index 45f1691e..0bc3347e 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java @@ -34,7 +34,13 @@ public Requester createRequesterForSingleResponse(String namespace, Class @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout) { + return createRequesterForSingleResponse(namespace, null, payloadClass, timeout); + } + + @Override + public Requester createRequesterForSingleResponse(String namespace, MessageTemplate messageTemplate, Class payloadClass, int timeout) { RequestOptions requestOptions = new RequestOptions.Builder() + .withMessageTemplate(messageTemplate) .withWaitForResponses(1) .withResponseTimeout(timeout) .withAckTimeout(0) @@ -99,6 +105,11 @@ public ResponderServer createResponderServer(String namespace, MessageTempla @Override public Requester createRequesterForFireAndForget(String namespace) { + return createRequesterForFireAndForget(namespace, null); + } + + @Override + public Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate) { RequestOptions requestOptions = new RequestOptions.Builder() .withWaitForResponses(0) .build(); From 85e49db23e993b3547f09aec1a3fa08c538712d7 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 24 May 2016 11:16:23 +0300 Subject: [PATCH 132/226] Minor change in ObjectFactory API --- .../io/github/tcdl/msb/api/ObjectFactory.java | 21 +++++++------------ .../tcdl/msb/impl/ObjectFactoryImpl.java | 21 ++++++++----------- .../objectfactory/TestMsbObjectFactory.java | 17 +++++++-------- 3 files changed, 24 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java index edaf9f06..0b1b0fef 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java +++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java @@ -45,28 +45,21 @@ public Type getType() { /** * Same as - * {@link io.github.tcdl.msb.api.ObjectFactory#createRequesterForSingleResponse(java.lang.String, java.lang.Class, int)} - * with default timeout + * {@link ObjectFactory#createRequesterForSingleResponse(java.lang.String, java.lang.Class, io.github.tcdl.msb.api.RequestOptions)} + * with default request options */ Requester createRequesterForSingleResponse(String namespace, Class payloadClass); - /** - * Same as - * {@link io.github.tcdl.msb.api.ObjectFactory#createRequesterForSingleResponse(java.lang.String, io.github.tcdl.msb.api.MessageTemplate, java.lang.Class, int)} - * with default messageTemplate - */ - Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout); - /** * Creates requester for single response with default response and acknowledgment timeouts * - * @param namespace topic name to send a request to - * @param payloadClass expected payload class of response messages - * @param messageTemplate {@link MessageTemplate} to be used - * @param timeout response timeout (in milliseconds) + * @param namespace topic name to send a request to + * @param payloadClass expected payload class of response messages + * @param baseRequestOptions request options to be used as a source of response timeout and {@link MessageTemplate}. + * Response time however will be 1 even if {@code baseRequestOptions} define other value. * @return new instance of a {@link Requester} with original message */ - Requester createRequesterForSingleResponse(String namespace, MessageTemplate messageTemplate, Class payloadClass, int timeout); + Requester createRequesterForSingleResponse(String namespace, Class payloadClass, RequestOptions baseRequestOptions); /** * Convenience method that specifies incoming payload type as {@link JsonNode} diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java index 85df167e..479292cc 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java @@ -48,26 +48,23 @@ public Requester createRequester(String namespace, RequestOptions request @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass) { MsbConfig msbConfig = msbContext.getMsbConfig(); - return createRequesterForSingleResponse(namespace, payloadClass, msbConfig.getDefaultResponseTimeout()); - } - /** - * {@inheritDoc} - */ - @Override - public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout) { - return createRequesterForSingleResponse(namespace, null, payloadClass, timeout); + RequestOptions requestOptions = new RequestOptions.Builder() + .withMessageTemplate(new MessageTemplate()) + .withResponseTimeout(msbConfig.getDefaultResponseTimeout()) + .build(); + + return createRequesterForSingleResponse(namespace, payloadClass, requestOptions); } /** * {@inheritDoc} */ @Override - public Requester createRequesterForSingleResponse(String namespace, MessageTemplate messageTemplate, Class payloadClass, int timeout) { - RequestOptions requestOptions = new RequestOptions.Builder() - .withMessageTemplate(messageTemplate) + public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, RequestOptions baseRequestOptions) { + + RequestOptions requestOptions = new RequestOptions.Builder().from(baseRequestOptions) .withWaitForResponses(1) - .withResponseTimeout(timeout) .withAckTimeout(0) .build(); return RequesterImpl.create(namespace, requestOptions, msbContext, toTypeReference(payloadClass)); diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java index 0bc3347e..a63df1f2 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java @@ -29,20 +29,19 @@ public Requester createRequester(String namespace, RequestOptions request @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass) { - return createRequesterForSingleResponse(namespace, payloadClass, 100); - } + RequestOptions requestOptions = new RequestOptions.Builder() + .withMessageTemplate(new MessageTemplate()) + .withResponseTimeout(100) + .build(); - @Override - public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, int timeout) { - return createRequesterForSingleResponse(namespace, null, payloadClass, timeout); + return createRequesterForSingleResponse(namespace, payloadClass, requestOptions); } @Override - public Requester createRequesterForSingleResponse(String namespace, MessageTemplate messageTemplate, Class payloadClass, int timeout) { - RequestOptions requestOptions = new RequestOptions.Builder() - .withMessageTemplate(messageTemplate) + public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, RequestOptions baseRequestOptions) { + + RequestOptions requestOptions = new RequestOptions.Builder().from(baseRequestOptions) .withWaitForResponses(1) - .withResponseTimeout(timeout) .withAckTimeout(0) .build(); From ef3df6fdf1418db011a8f72f75b3b7a6d7442d68 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 24 May 2016 12:13:20 +0300 Subject: [PATCH 133/226] Review suggestions --- .../tcdl/msb/api/MsbContextBuilder.java | 2 +- .../github/tcdl/msb/collector/Collector.java | 2 +- .../ExecutionOptionsAwareMessageHandler.java | 2 +- .../DirectInvocationCapableInvoker.java | 11 ++--- .../DirectInvocationCapableInvokerTest.java | 46 +++++++++---------- 5 files changed, 28 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java index 8db30cff..c4419511 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java +++ b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java @@ -165,7 +165,7 @@ private MessageHandlerInvoker createMessageHandlerInvoker(AdapterFactory adapter } else { consumerMessageHandlerInvoker = new DirectMessageHandlerInvoker(); } - return new DirectInvocationCapableInvoker(consumerMessageHandlerInvoker); + return new DirectInvocationCapableInvoker(consumerMessageHandlerInvoker, new DirectMessageHandlerInvoker()); } /** diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index 7bf7a9cb..f1d51c1c 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -417,7 +417,7 @@ Message getRequestMessage() { } @Override - public boolean isDirectlyInvokable(){ + public boolean forceDirectInvocation(){ return directlyInvokable; } } diff --git a/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java b/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java index ea122305..5b97addc 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/ExecutionOptionsAwareMessageHandler.java @@ -12,5 +12,5 @@ public interface ExecutionOptionsAwareMessageHandler extends MessageHandler { * Indicates whether handler should be executed by main message handling thread (true if so) or by thread from * consumer thread pool. */ - boolean isDirectlyInvokable(); + boolean forceDirectInvocation(); } \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java b/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java index 93da4ec7..4c815347 100644 --- a/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java +++ b/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java @@ -19,14 +19,11 @@ public class DirectInvocationCapableInvoker implements MessageHandlerInvoker { * Creates composite delegate that is guarantied to have an instance of {@link DirectMessageHandlerInvoker} in its disposal. * There is no need to instantiate it in client code. It is intended to be used only internally by the library. */ - public DirectInvocationCapableInvoker(MessageHandlerInvoker clientMessageHandlerInvoker) { + public DirectInvocationCapableInvoker(MessageHandlerInvoker clientMessageHandlerInvoker, DirectMessageHandlerInvoker directMessageHandlerInvoker) { Validate.notNull(clientMessageHandlerInvoker); + Validate.notNull(directMessageHandlerInvoker); this.clientMessageHandlerInvoker = clientMessageHandlerInvoker; - if (clientMessageHandlerInvoker instanceof DirectMessageHandlerInvoker) { - this.directMessageHandlerInvoker = (DirectMessageHandlerInvoker) clientMessageHandlerInvoker; - } else { - this.directMessageHandlerInvoker = new DirectMessageHandlerInvoker(); - } + this.directMessageHandlerInvoker = directMessageHandlerInvoker; } /** @@ -34,7 +31,7 @@ public DirectInvocationCapableInvoker(MessageHandlerInvoker clientMessageHandler */ @Override public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) { - if (messageHandler instanceof ExecutionOptionsAwareMessageHandler && ((ExecutionOptionsAwareMessageHandler) messageHandler).isDirectlyInvokable()) { + if (messageHandler instanceof ExecutionOptionsAwareMessageHandler && ((ExecutionOptionsAwareMessageHandler) messageHandler).forceDirectInvocation()) { directMessageHandlerInvoker.execute(messageHandler, message, acknowledgeHandler); } else { clientMessageHandlerInvoker.execute(messageHandler, message, acknowledgeHandler); diff --git a/core/src/test/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvokerTest.java b/core/src/test/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvokerTest.java index 8ca6a803..0704ec2d 100644 --- a/core/src/test/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvokerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvokerTest.java @@ -5,6 +5,7 @@ import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.collector.ExecutionOptionsAwareMessageHandler; import io.github.tcdl.msb.support.TestUtils; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -17,47 +18,49 @@ @RunWith(MockitoJUnitRunner.class) public class DirectInvocationCapableInvokerTest { - DirectInvocationCapableInvoker instance; + final String namespace = "some:namespace"; @Mock AcknowledgementHandlerInternal ackHandler; + @Mock + MessageHandlerInvoker clientMessageHandlerInvoker; + @Mock + DirectMessageHandlerInvoker directMessageHandlerInvoker; + + DirectInvocationCapableInvoker instance; + + @Before + public void setUp() throws Exception { + instance = new DirectInvocationCapableInvoker(clientMessageHandlerInvoker, directMessageHandlerInvoker); + } @Test public void execute_shouldUseInternalDirectInvoker() throws Exception { - MessageHandlerInvoker clientMessageHandlerInvoker = mock(MessageHandlerInvoker.class); - instance = new DirectInvocationCapableInvoker(clientMessageHandlerInvoker); - ExecutionOptionsAwareMessageHandler messageHandler = mock(ExecutionOptionsAwareMessageHandler.class); - when(messageHandler.isDirectlyInvokable()).thenReturn(true); + when(messageHandler.forceDirectInvocation()).thenReturn(true); Message message = TestUtils.createSimpleRequestMessage(namespace); instance.execute(messageHandler, message, ackHandler); - verify(messageHandler).handleMessage(eq(message), eq(ackHandler)); + verify(directMessageHandlerInvoker).execute(eq(messageHandler), eq(message), eq(ackHandler)); verify(clientMessageHandlerInvoker, never()).execute(any(MessageHandler.class), any(Message.class), any(AcknowledgementHandlerInternal.class)); } @Test public void execute_shouldUseClientInvoker_whenNoDirectInvocationNeeded() throws Exception { - - MessageHandlerInvoker clientMessageHandlerInvoker = mock(MessageHandlerInvoker.class); - instance = new DirectInvocationCapableInvoker(clientMessageHandlerInvoker); - ExecutionOptionsAwareMessageHandler messageHandler = mock(ExecutionOptionsAwareMessageHandler.class); - when(messageHandler.isDirectlyInvokable()).thenReturn(false); + when(messageHandler.forceDirectInvocation()).thenReturn(false); Message message = TestUtils.createSimpleRequestMessage(namespace); instance.execute(messageHandler, message, ackHandler); verify(clientMessageHandlerInvoker).execute(eq(messageHandler), eq(message), eq(ackHandler)); + verify(directMessageHandlerInvoker, never()).execute(any(MessageHandler.class), any(Message.class), any(AcknowledgementHandlerInternal.class)); } @Test public void execute_shouldUseClientInvoker_whenHandlerIsNotDirectlyInvokable() throws Exception { - MessageHandlerInvoker clientMessageHandlerInvoker = mock(MessageHandlerInvoker.class); - instance = new DirectInvocationCapableInvoker(clientMessageHandlerInvoker); - MessageHandler messageHandler = mock(MessageHandler.class); Message message = TestUtils.createSimpleRequestMessage(namespace); @@ -67,16 +70,9 @@ public void execute_shouldUseClientInvoker_whenHandlerIsNotDirectlyInvokable() t } @Test - public void execute_shouldUseClientDirectInvoker() throws Exception { - DirectMessageHandlerInvoker clientMessageHandlerInvoker = mock(DirectMessageHandlerInvoker.class); - instance = new DirectInvocationCapableInvoker(clientMessageHandlerInvoker); - - ExecutionOptionsAwareMessageHandler messageHandler = mock(ExecutionOptionsAwareMessageHandler.class); - when(messageHandler.isDirectlyInvokable()).thenReturn(true); - - Message message = TestUtils.createSimpleRequestMessage(namespace); - instance.execute(messageHandler, message, ackHandler); - - verify(clientMessageHandlerInvoker).execute(eq(messageHandler), eq(message), eq(ackHandler)); + public void shutdown() throws Exception { + instance.shutdown(); + verify(clientMessageHandlerInvoker).shutdown(); + verify(directMessageHandlerInvoker).shutdown(); } } \ No newline at end of file From 1645027aff3982eca23ee7f4c113642d029288ba Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Thu, 26 May 2016 17:05:49 +0300 Subject: [PATCH 134/226] Updated release-notes --- cli/src/main/resources/application.conf | 11 ++++++----- release-notes.html | 12 +++++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/cli/src/main/resources/application.conf b/cli/src/main/resources/application.conf index 01784b50..5e168911 100644 --- a/cli/src/main/resources/application.conf +++ b/cli/src/main/resources/application.conf @@ -12,18 +12,19 @@ msbConfig { # Thread pool used for scheduling ack and response timeout tasks timerThreadPoolSize: 2 + threadingConfig = { + consumerThreadPoolSize = 5 + # -1 means unlimited + consumerThreadPoolQueueCapacity = 20 + } + # Broker Adapter Defaults brokerConfig = { host = "127.0.0.1" port = "5672" groupId = "cli-tool" durable = false - consumerThreadPoolSize = 5 - # -1 means unlimited - consumerThreadPoolQueueCapacity = 20 prefetchCount = 0 } } - - diff --git a/release-notes.html b/release-notes.html index 1c154c79..866bd7f2 100644 --- a/release-notes.html +++ b/release-notes.html @@ -4,17 +4,19 @@ -

Welcome to MSB-Java version 1.4.7

+

Welcome to MSB-Java version 1.5.0

-

April 12, 2016

+

May 26, 2016

 
-Features of MSB-Java version 1.4.7:
+Features of MSB-Java version 1.5.0:
    - Multithreading settings parameters consumerThreadPoolSize, consumerThreadPoolQueueCapacity moved
-    from "brokerConfig" to "threadingConfig" config section.
+     from "brokerConfig" to "threadingConfig" config section;
    - Now it is possible to process a group of messages in a single-threaded mode using
-    MsbContextBuilder.withMessageGroupStrategy().
+     MsbContextBuilder.withMessageGroupStrategy();
+   - Added Requester.request() method that is similar to Requester.publish() but expects exactly 
+     one response and returns CompletableFuture.
 
 Features of MSB-Java version 1.4.6:
    - Updated messages schema validation;

From 9deb466d9c89ea2c078b169dedeacc4d1cc6d4f0 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Thu, 26 May 2016 18:26:15 +0300
Subject: [PATCH 135/226] [maven-release-plugin] prepare release msb-java-1.5.0

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 pom.xml            | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index f553a68e..2a9c478d 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.7-SNAPSHOT
+        1.5.0
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.0
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index b19b5fb0..ffd2f8fe 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.7-SNAPSHOT
+        1.5.0
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.0
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 306d7c13..7ee242aa 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.7-SNAPSHOT
+        1.5.0
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.0
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 3022169d..acbc6ee9 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.7-SNAPSHOT
+        1.5.0
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.0
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index a170eba3..8fb43624 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.4.7-SNAPSHOT
+        1.5.0
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.0
     
 
     
diff --git a/pom.xml b/pom.xml
index d28530bd..845b438a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.4.7-SNAPSHOT
+    1.5.0
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.0
     
     
         

From 55a762e9b9addb96f098c9bdd2441e2b0abb8e80 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Thu, 26 May 2016 18:26:21 +0300
Subject: [PATCH 136/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 pom.xml            | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 2a9c478d..c1653cdf 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.0
+        1.5.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.0
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index ffd2f8fe..2e0c24a4 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.0
+        1.5.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.0
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 7ee242aa..d6f55ccf 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.0
+        1.5.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.0
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index acbc6ee9..172d5bd5 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.0
+        1.5.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.0
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 8fb43624..1e5bf27f 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.0
+        1.5.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.0
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index 845b438a..76f9be9c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.5.0
+    1.5.1-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.0
+        HEAD
     
     
         

From 4f978d3bc10cbbd754427a521fd44424b9f78ded Mon Sep 17 00:00:00 2001
From: Andrii Grytsyk 
Date: Tue, 12 Jul 2016 16:04:35 +0300
Subject: [PATCH 137/226] add request object and map into MsbThreadContext

---
 .../tcdl/msb/impl/MsbThreadContext.java       | 35 +++++++++++++++++++
 .../tcdl/msb/impl/ResponderServerImpl.java    |  4 ++-
 .../msb/impl/ResponderServerImplTest.java     | 10 ++++--
 3 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java b/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java
index 4246a6b9..2436f30f 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/MsbThreadContext.java
@@ -3,6 +3,9 @@
 
 import io.github.tcdl.msb.api.MessageContext;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Gives access to initial message without polluting client classes APIs MsbThreadContext is wrapper around {@link ThreadLocal}. Additional care has to be taken
  * if any kind of multithreaded message processing takes place.
@@ -10,6 +13,13 @@
 public class MsbThreadContext {
 
     private static final ThreadLocal messageContext = new ThreadLocal<>();
+    private static final ThreadLocal request = new ThreadLocal<>();
+    private static final ThreadLocal> map = new ThreadLocal>(){
+        @Override
+        protected Map initialValue() {
+            return new HashMap<>();
+        }
+    };
 
     public static MessageContext getMessageContext() {
         return messageContext.get();
@@ -19,7 +29,32 @@ public static void setMessageContext(MessageContext messageContext) {
         MsbThreadContext.messageContext.set(messageContext);
     }
 
+    public static Object getRequest() {
+        return request.get();
+    }
+
+    public static void setRequest(Object request) {
+        MsbThreadContext.request.set(request);
+    }
+
+
+    public static Map getMap() {
+        return map.get();
+    }
+
+    public static void put(String key, Object value){
+        if(key == null) {
+            throw new IllegalArgumentException("key cannot be null");
+        }
+
+        map.get().put(key, value);
+    }
+
+
     static void clear() {
         messageContext.remove();
+        request.remove();
+        map.remove();
     }
+
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
index 3bc35ca0..6ba10f2f 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
@@ -86,8 +86,10 @@ void onResponder(ResponderContext responderContext) {
         Message originalMessage = responderContext.getOriginalMessage();
         Object rawPayload = originalMessage.getRawPayload();
         try {
-            MsbThreadContext.setMessageContext(responderContext);
             T request = Utils.convert(rawPayload, payloadTypeReference, payloadMapper);
+            MsbThreadContext.setMessageContext(responderContext);
+            MsbThreadContext.setRequest(request);
+
             LOG.debug("[{}] Process message with id: [{}]", namespace, originalMessage.getId());
             requestHandler.process(request, responderContext);
         } catch (Exception e) {
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
index 8939ef25..56bc5242 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
@@ -53,8 +53,12 @@ public void testResponderServerProcessPayloadSuccess() throws Exception {
         Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC);
 
         ResponderServer.RequestHandler, Object, Map>> handler =
-                (request, responderContext) -> assertEquals("MessageContext must contain original message during message handler execution", originalMessage,
-                MsbThreadContext.getMessageContext().getOriginalMessage());
+                (request, responderContext) -> {
+                    assertEquals("MessageContext must contain original message during message handler execution",
+                            originalMessage, MsbThreadContext.getMessageContext().getOriginalMessage());
+                    assertEquals("MsbThreadContext must contain a Request during message handler execution",
+                            request, MsbThreadContext.getRequest());
+                };
 
         ArgumentCaptor subscriberCaptor = ArgumentCaptor.forClass(MessageHandler.class);
         ChannelManager spyChannelManager = spy(msbContext.getChannelManager());
@@ -71,8 +75,10 @@ public void testResponderServerProcessPayloadSuccess() throws Exception {
         verify(spyChannelManager).subscribe(anyString(), subscriberCaptor.capture());
 
         assertNull("MessageContext must be absent outside message handler execution", MsbThreadContext.getMessageContext());
+        assertNull("Request must be absent outside message handler execution", MsbThreadContext.getRequest());
         subscriberCaptor.getValue().handleMessage(originalMessage, null);
         assertNull("MessageContext must be absent outside message handler execution", MsbThreadContext.getMessageContext());
+        assertNull("Request must be absent outside message handler execution", MsbThreadContext.getRequest());
 
         verify(spyResponderServer).onResponder(anyObject());
     }

From 8b19ee678da1bdfc074eff776c11a652e81c2dc0 Mon Sep 17 00:00:00 2001
From: alex 
Date: Thu, 21 Jul 2016 11:05:42 +0300
Subject: [PATCH 138/226] Added stop() method to ResponderServer

---
 .../io/github/tcdl/msb/ChannelManager.java     |  4 +---
 .../github/tcdl/msb/api/ResponderServer.java   |  7 ++++++-
 .../github/tcdl/msb/impl/MsbContextImpl.java   |  2 +-
 .../tcdl/msb/impl/ResponderServerImpl.java     |  7 +++++++
 .../tcdl/msb/impl/ResponderServerImplTest.java | 18 +++++++++++++++++-
 5 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
index 610e4d70..10fb52ad 100644
--- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
@@ -109,10 +109,8 @@ public synchronized boolean subscribeForResponses(String topic, CollectorManager
     /**
      * Stop consuming messages on specified topic.
      * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages.
-     *
-     * @param topic
      */
-    public void unsubscribe(String topic) {
+    public synchronized void unsubscribe(String topic) {
         Consumer consumer = consumersByTopic.remove(topic);
         if (consumer != null) {
             consumer.end();
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java b/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
index 96fc4318..e783ea7e 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
@@ -17,7 +17,12 @@ public interface ResponderServer {
     /**
      * Start listening for message on specified topic.
      */
-     ResponderServer listen();
+    ResponderServer listen();
+
+    /**
+     * Stop listening
+     */
+    ResponderServer stop();
 
     /**
      * Implementation of this interface contains business logic processed by microservice.
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java
index e5548169..395fe2de 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java
@@ -57,9 +57,9 @@ public synchronized void shutdown() {
             isShutdownComplete = true;
             LOG.info("Shutting down MSB context...");
             shutdownCallbackHandler.runCallbacks();
+            channelManager.shutdown();
             objectFactory.shutdown();
             timeoutManager.shutdown();
-            channelManager.shutdown();
             LOG.info("MSB context has been shut down.");
         } else {
             LOG.warn("Trying to shutdown MsbContext several times");
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
index 6ba10f2f..df85d12a 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
@@ -70,6 +70,13 @@ public ResponderServer listen() {
         return this;
     }
 
+    @Override
+    public ResponderServer stop(){
+        ChannelManager channelManager = msbContext.getChannelManager();
+        channelManager.unsubscribe(namespace);
+        return this;
+    }
+
     Responder createResponder(Message incomingMessage) {
         if (isResponseNeeded(incomingMessage)) {
             return new ResponderImpl(messageTemplate, incomingMessage, msbContext);
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
index 56bc5242..7c98cdac 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
@@ -121,7 +121,6 @@ public void testResponderServerProcessHandlerThrowException() throws Exception {
         responderServer.listen();
 
         // simulate incoming request
-        ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(RestPayload.class);
         Message originalMessage = TestUtils.createMsbRequestMessageNoPayload(TOPIC);
         ResponderImpl responder = spy(
                 new ResponderImpl(messageTemplate, originalMessage, msbContext));
@@ -206,4 +205,21 @@ public void testCreateResponderNoResponseTopic() {
         // Verify that no messages were published
         verifyZeroInteractions(mockChannelManager);
     }
+
+    @Test
+    public void testStop() throws Exception {
+        ResponderServer.RequestHandler doNothingHandler = (request, responderContext) -> {};
+
+        ChannelManager mockChannelManager = mock(ChannelManager.class);
+        MsbContextImpl msbContext = new TestUtils.TestMsbContextBuilder()
+                .withChannelManager(mockChannelManager)
+                .build();
+
+        ResponderServerImpl responderServer = ResponderServerImpl.create(
+                TOPIC, messageTemplate, msbContext, doNothingHandler, null, new TypeReference() {}
+        );
+
+        responderServer.stop();
+        verify(mockChannelManager).unsubscribe(TOPIC);
+    }
 }

From d247ffbbb24c974ddeda9c286783a58bfaf5b238 Mon Sep 17 00:00:00 2001
From: alex 
Date: Tue, 26 Jul 2016 13:21:56 +0300
Subject: [PATCH 139/226] fixed broken graceful shutdown of TimeoutManager on
 MsbContextImpl shutdown

---
 core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java
index 395fe2de..e5548169 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java
@@ -57,9 +57,9 @@ public synchronized void shutdown() {
             isShutdownComplete = true;
             LOG.info("Shutting down MSB context...");
             shutdownCallbackHandler.runCallbacks();
-            channelManager.shutdown();
             objectFactory.shutdown();
             timeoutManager.shutdown();
+            channelManager.shutdown();
             LOG.info("MSB context has been shut down.");
         } else {
             LOG.warn("Trying to shutdown MsbContext several times");

From 65ad562dac68ba2e6e4c3cf0408fd67c174cf477 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 29 Jul 2016 17:32:02 +0300
Subject: [PATCH 140/226] Prepared for 1.5.1 release

---
 release-notes.html | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/release-notes.html b/release-notes.html
index 866bd7f2..cd359683 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -4,11 +4,15 @@
 
 
 
-

Welcome to MSB-Java version 1.5.0

+

Welcome to MSB-Java version 1.5.1

-

May 26, 2016

+

July 29, 2016

+Features of MSB-Java version 1.5.1:
+   - Added stop() method to ResponderServer
+   - Add request object and map into MsbThreadContext
+   - Fixed broken graceful shutdown of TimeoutManager on MsbContextImpl shutdown
 
 Features of MSB-Java version 1.5.0:
    - Multithreading settings parameters consumerThreadPoolSize, consumerThreadPoolQueueCapacity moved

From 3aa62d53fcf5faffeaf6ec2682f134b557ad0df9 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Sun, 31 Jul 2016 18:48:51 +0300
Subject: [PATCH 141/226] [maven-release-plugin] prepare release msb-java-1.5.1

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 pom.xml            | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index c1653cdf..cfcc75d6 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.1-SNAPSHOT
+        1.5.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.1
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 2e0c24a4..13114be5 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.1-SNAPSHOT
+        1.5.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.1
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index d6f55ccf..36748527 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.1-SNAPSHOT
+        1.5.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.1
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 172d5bd5..d2422ba4 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.1-SNAPSHOT
+        1.5.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.1
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 1e5bf27f..addf41fb 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.1-SNAPSHOT
+        1.5.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.1
     
 
     
diff --git a/pom.xml b/pom.xml
index 76f9be9c..3f4da2ef 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.5.1-SNAPSHOT
+    1.5.1
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.1
     
     
         

From b8f1e5e4c6189f8da26afe42ad3c312bac77e90b Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Sun, 31 Jul 2016 18:48:57 +0300
Subject: [PATCH 142/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 pom.xml            | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index cfcc75d6..ea2b14c6 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.1
+        1.5.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.1
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 13114be5..8e226126 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.1
+        1.5.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.1
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 36748527..92ff32fc 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.1
+        1.5.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.1
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index d2422ba4..5a83c822 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.1
+        1.5.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.1
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index addf41fb..ac6115d7 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.1
+        1.5.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.1
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index 3f4da2ef..f72740c0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.5.1
+    1.5.2-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.1
+        HEAD
     
     
         

From f85e87d1e1fe71380c687beac25661b2a9f69af3 Mon Sep 17 00:00:00 2001
From: rdro-tc 
Date: Wed, 3 Aug 2016 11:56:31 +0300
Subject: [PATCH 143/226] Plugin for Apache jmeter

---
 .../github/tcdl/msb/examples/PongService.java |   9 +-
 jmeter/pom.xml                                |  80 +++++++
 .../jmeter/sampler/MsbRequesterSampler.java   | 144 ++++++++++++
 .../msb/jmeter/sampler/RequesterConfig.java   | 129 +++++++++++
 .../sampler/gui/MsbRequesterSamplerGui.java   |  73 ++++++
 .../sampler/gui/RequesterConfigForm.java      | 213 ++++++++++++++++++
 .../gui/validation/IntegerVerifier.java       |  39 ++++
 .../sampler/gui/validation/JsonVerifier.java  |  51 +++++
 .../gui/validation/NotBlankVerifier.java      |  25 ++
 .../gui/validation/PatternVerifier.java       |  42 ++++
 jmeter/src/main/resources/application.conf    |  20 ++
 jmeter/src/main/resources/logback.xml         |  27 +++
 .../tcdl/msb/jmeter/sampler/UiTest.java       |  18 ++
 .../examples/MSB Requester Sampler.jmx        |  90 ++++++++
 .../examples}/SearchForYear_100Users.jmx      |   0
 .../examples}/SearchForYear_10Users.jmx       |   0
 pom.xml                                       |   1 +
 17 files changed, 954 insertions(+), 7 deletions(-)
 create mode 100644 jmeter/pom.xml
 create mode 100644 jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/MsbRequesterSampler.java
 create mode 100644 jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/RequesterConfig.java
 create mode 100644 jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/MsbRequesterSamplerGui.java
 create mode 100644 jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/RequesterConfigForm.java
 create mode 100644 jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/IntegerVerifier.java
 create mode 100644 jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/JsonVerifier.java
 create mode 100644 jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/NotBlankVerifier.java
 create mode 100644 jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/PatternVerifier.java
 create mode 100644 jmeter/src/main/resources/application.conf
 create mode 100644 jmeter/src/main/resources/logback.xml
 create mode 100644 jmeter/src/test/java/io/github/tcdl/msb/jmeter/sampler/UiTest.java
 create mode 100644 jmeter/src/test/resources/examples/MSB Requester Sampler.jmx
 rename jmeter/{ => src/test/resources/examples}/SearchForYear_100Users.jmx (100%)
 rename jmeter/{ => src/test/resources/examples}/SearchForYear_10Users.jmx (100%)

diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java b/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java
index c82fb47c..338fbfb5 100644
--- a/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java
+++ b/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java
@@ -1,11 +1,6 @@
 package io.github.tcdl.msb.examples;
 
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.MsbContext;
-import io.github.tcdl.msb.api.MsbContextBuilder;
-import io.github.tcdl.msb.api.ObjectFactory;
-import io.github.tcdl.msb.api.ResponderServer;
-
+import io.github.tcdl.msb.api.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -19,7 +14,7 @@ public static void main(String[] args) {
 
         ObjectFactory objectFactory = msbContext.getObjectFactory();
         MessageTemplate messageTemplate = new MessageTemplate().withTags("pong-static-tag");
-        ResponderServer responderServer = objectFactory.createResponderServer("pingpong:namespace", messageTemplate, 
+        ResponderServer responderServer = objectFactory.createResponderServer("pingpong:namespace", messageTemplate,
                 (request, responderContext) -> {
             // Response handling logic
             LOG.info(String.format("Handling %s...", request));
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
new file mode 100644
index 00000000..eb71c28b
--- /dev/null
+++ b/jmeter/pom.xml
@@ -0,0 +1,80 @@
+
+
+
+    4.0.0
+
+    
+        io.github.tcdl.msb
+        msb-java
+        1.5.2-SNAPSHOT
+        ../pom.xml
+    
+
+    ApacheJmeter_msb
+    jar
+
+    
+        scm:git:https://github.com/tcdl/msb-java.git
+        scm:git:git@github.com:tcdl/msb-java.git
+        https://github.com/tcdl/msb-java
+        HEAD
+    
+
+    
+        tcdl
+        https://github.com/tcdl
+    
+
+    
+        UTF-8
+        UTF-8
+        3.0
+    
+
+    
+        
+            org.apache.jmeter
+            ApacheJMeter_core
+            ${apache.jmeter.version}
+            provided
+        
+
+        
+            io.github.tcdl.msb
+            msb-java-core
+        
+        
+            io.github.tcdl.msb
+            msb-java-amqp
+        
+    
+
+    
+        install
+        
+            
+                maven-jar-plugin
+            
+            
+                org.apache.maven.plugins
+                maven-shade-plugin
+                2.4.3
+                
+                    
+                        package
+                        
+                            shade
+                        
+                        
+                            ${project.artifactId}-${project.version}
+                            true
+                        
+                    
+                
+            
+        
+    
+
+
\ No newline at end of file
diff --git a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/MsbRequesterSampler.java b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/MsbRequesterSampler.java
new file mode 100644
index 00000000..3995dfd4
--- /dev/null
+++ b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/MsbRequesterSampler.java
@@ -0,0 +1,144 @@
+
+package io.github.tcdl.msb.jmeter.sampler;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigValueFactory;
+import io.github.tcdl.msb.api.MessageTemplate;
+import io.github.tcdl.msb.api.MsbContext;
+import io.github.tcdl.msb.api.MsbContextBuilder;
+import io.github.tcdl.msb.api.RequestOptions;
+import io.github.tcdl.msb.support.Utils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jmeter.samplers.AbstractSampler;
+import org.apache.jmeter.samplers.Entry;
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MsbRequesterSampler extends AbstractSampler {
+
+    private static final Logger LOG = LoggingManager.getLoggerForClass();
+
+    private static AtomicInteger classCount = new AtomicInteger(0);
+
+    private String MSB_BROKER_CONFIG_ROOT = "msbConfig.brokerConfig";
+
+    private MsbContext msbContext;
+
+    private ObjectMapper objectMapper = new ObjectMapper()
+            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
+            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+            .setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+    public MsbRequesterSampler() {
+        classCount.incrementAndGet();
+        trace("MsbRequesterSampler()");
+    }
+
+    public void init() {
+        // do nothing
+    }
+
+    private void initMsb(RequesterConfig requesterConfig) {
+        Config msbConfig = ConfigFactory.load()
+                .withValue(MSB_BROKER_CONFIG_ROOT + ".host", ConfigValueFactory.fromAnyRef(requesterConfig.getHost()))
+                .withValue(MSB_BROKER_CONFIG_ROOT + ".port", ConfigValueFactory.fromAnyRef(requesterConfig.getPort()))
+                .withValue(MSB_BROKER_CONFIG_ROOT + ".virtualHost", ConfigValueFactory.fromAnyRef(requesterConfig.getVirtualHost()))
+                .withValue(MSB_BROKER_CONFIG_ROOT + ".username", ConfigValueFactory.fromAnyRef(requesterConfig.getUserName()))
+                .withValue(MSB_BROKER_CONFIG_ROOT + ".password", ConfigValueFactory.fromAnyRef(requesterConfig.getPassword()));
+
+        msbContext = new MsbContextBuilder()
+                .withConfig(msbConfig)
+                .enableShutdownHook(true)
+                .build();
+    }
+
+    public SampleResult sample(Entry e) {
+        trace("sample()");
+
+        RequesterConfig requesterConfig = (RequesterConfig)getProperty(RequesterConfig.TEST_ELEMENT_CONFIG).getObjectValue();
+        if (msbContext == null) {
+            initMsb(requesterConfig);
+        }
+
+        String namespace = requesterConfig.getNamespace();
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withMessageTemplate(new MessageTemplate())
+                .withResponseTimeout(requesterConfig.getTimeout())
+                .withForwardNamespace(requesterConfig.getForwardNamespace())
+                .withWaitForResponses(requesterConfig.getWaitForResponses() ? requesterConfig.getNumberOfResponses() : 0)
+                .build();
+
+        String payloadJson = StringUtils.isNotEmpty(requesterConfig.getRequestPayload()) ? requesterConfig.getRequestPayload() : "{}";
+        JsonNode payload = Utils.fromJson(payloadJson, JsonNode.class, objectMapper);
+
+        final CountDownLatch waitForResponse = new CountDownLatch(requesterConfig.getNumberOfResponses());
+        final ArrayNode responses = objectMapper.createArrayNode();
+
+        SampleResult res = new SampleResult();
+        res.setSampleLabel(this.getName());
+
+        res.sampleStart();
+
+        msbContext
+                .getObjectFactory()
+                .createRequester(namespace, requestOptions, JsonNode.class)
+                .onResponse((response, messageContext) -> {
+                        waitForResponse.countDown();
+                        responses.add(response);
+                })
+                .publish(payload);
+
+        if (requesterConfig.getWaitForResponses()) {
+            try {
+                waitForResponse.await(2 * requesterConfig.getTimeout(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException ie) {
+                res.setResponseMessage(ie.getMessage());
+            }
+        }
+
+        res.sampleEnd();
+
+        int numberOfReceivedResponses = requesterConfig.getNumberOfResponses() - (int) waitForResponse.getCount();
+        trace("Received " + numberOfReceivedResponses + " responses from " + requesterConfig.getNumberOfResponses());
+        boolean isResultOk = !requesterConfig.getWaitForResponses() || waitForResponse.getCount() == 0;
+
+        if (isResultOk) {
+            String responsesAsJson = responses.toString();
+            trace("Responses: " + responsesAsJson);
+
+            res.setSuccessful(true);
+            res.setResponseCodeOK();
+            res.setResponseMessageOK();
+
+            res.setDataType(SampleResult.TEXT);
+            res.setResponseData(responsesAsJson, null);
+        } else {
+            res.setSuccessful(false);
+        }
+
+        return res;
+    }
+
+    private void trace(String message) {
+        LOG.info(Thread.currentThread().getName() + " (" + classCount.get() + ") " + this.getName() + " " + message);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (msbContext != null) {
+            msbContext.shutdown();
+        }
+    }
+}
\ No newline at end of file
diff --git a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/RequesterConfig.java b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/RequesterConfig.java
new file mode 100644
index 00000000..2648c138
--- /dev/null
+++ b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/RequesterConfig.java
@@ -0,0 +1,129 @@
+package io.github.tcdl.msb.jmeter.sampler;
+
+import static io.github.tcdl.msb.support.Utils.ifNull;
+import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
+
+/**
+ * Created by rdro-tc on 28.07.16.
+ */
+public class RequesterConfig {
+
+    public final static String TEST_ELEMENT_CONFIG = "TestElement.msb_requester";
+
+    private String host;
+    private Integer port;
+    private String virtualHost;
+    private String userName;
+    private String password;
+    private String namespace;
+    private String forwardNamespace;
+    private Boolean waitForResponses;
+    private Integer numberOfResponses;
+    private Integer timeout;
+    private String requestPayload;
+
+    public RequesterConfig() {
+        setDefaults();
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public Integer getPort() {
+        return port;
+    }
+
+    public void setPort(Integer port) {
+        this.port = port;
+    }
+
+    public String getVirtualHost() {
+        return virtualHost;
+    }
+
+    public void setVirtualHost(String virtualHost) {
+        this.virtualHost = virtualHost;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public void setNamespace(String namespace) {
+        this.namespace = namespace;
+    }
+
+    public String getForwardNamespace() {
+        return forwardNamespace;
+    }
+
+    public void setForwardNamespace(String forwardNamespace) {
+        this.forwardNamespace = forwardNamespace;
+    }
+
+    public Boolean getWaitForResponses() {
+        return waitForResponses;
+    }
+
+    public void setWaitForResponses(Boolean waitForResponses) {
+        this.waitForResponses = waitForResponses;
+    }
+
+    public Integer getNumberOfResponses() {
+        return numberOfResponses;
+    }
+
+    public void setNumberOfResponses(Integer numberOfResponses) {
+        this.numberOfResponses = numberOfResponses;
+    }
+
+    public Integer getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(Integer timeout) {
+        this.timeout = timeout;
+    }
+
+    public String getRequestPayload() {
+        return requestPayload;
+    }
+
+    public void setRequestPayload(String requestPayload) {
+        this.requestPayload = requestPayload;
+    }
+
+    public void setDefaults() {
+        this.host = defaultIfBlank(this.host, "localhost");
+        this.port = this.port == null || this.port <= 0 ? 5672 : this.port;
+        this.virtualHost = defaultIfBlank(this.virtualHost, "/");
+        this.userName = defaultIfBlank(this.userName, "guest");
+        this.password = defaultIfBlank(this.password, "guest");
+        this.namespace = defaultIfBlank(this.namespace, "jmeter:test");
+        this.waitForResponses = ifNull(this.waitForResponses, true);
+        this.numberOfResponses = ifNull(this.numberOfResponses, 1);
+        this.timeout =  this.timeout == null || this.timeout <= 0 ? 3000 : this.timeout;
+        this.requestPayload = defaultIfBlank(this.requestPayload, "{}");
+    }
+}
diff --git a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/MsbRequesterSamplerGui.java b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/MsbRequesterSamplerGui.java
new file mode 100644
index 00000000..f98f1810
--- /dev/null
+++ b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/MsbRequesterSamplerGui.java
@@ -0,0 +1,73 @@
+package io.github.tcdl.msb.jmeter.sampler.gui;
+
+import io.github.tcdl.msb.jmeter.sampler.MsbRequesterSampler;
+import io.github.tcdl.msb.jmeter.sampler.RequesterConfig;
+import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
+import org.apache.jmeter.testelement.TestElement;
+import org.apache.jmeter.testelement.property.ObjectProperty;
+
+import java.awt.*;
+
+public class MsbRequesterSamplerGui extends AbstractSamplerGui {
+
+    private RequesterConfigForm configForm;
+
+    public MsbRequesterSamplerGui() {
+        init();
+    }
+
+    public String getName() {
+        return "MSB Requester Sampler";
+    }
+
+    public String getLabelResource() {
+        return null;
+    }
+
+    public String getStaticLabel() {
+        return "MSB Requester Sampler";
+    }
+
+    public void configure(TestElement testElement) {
+        super.configure(testElement);
+
+        MsbRequesterSampler sampler = (MsbRequesterSampler) testElement;
+        RequesterConfig config = (RequesterConfig)sampler.getProperty(RequesterConfig.TEST_ELEMENT_CONFIG).getObjectValue();
+
+        if (config == null) {
+            sampler.setProperty(new ObjectProperty(RequesterConfig.TEST_ELEMENT_CONFIG, configForm.getConfig()));
+        } else {
+            configForm.setConfig(config);
+        }
+
+        sampler.init();
+    }
+
+    public TestElement createTestElement() {
+        MsbRequesterSampler sampler = new MsbRequesterSampler();
+        modifyTestElement(sampler);
+        return sampler;
+    }
+
+    public void modifyTestElement(TestElement testElement) {
+        configureTestElement(testElement);
+        MsbRequesterSampler sampler = (MsbRequesterSampler) testElement;
+        sampler.setProperty(new ObjectProperty(RequesterConfig.TEST_ELEMENT_CONFIG, configForm.getConfig()));
+    }
+
+    private void init() {
+        setLayout(new BorderLayout(0, 0));
+        setBorder(makeBorder());
+        add(makeTitlePanel(), BorderLayout.NORTH);
+
+        RequesterConfig config = new RequesterConfig();
+        configForm = new RequesterConfigForm(config);
+        add(configForm.getUIComponent(), BorderLayout.WEST);
+    }
+
+    @Override
+    public void clearGui() {
+        configForm.setConfig(new RequesterConfig());
+        super.clearGui();
+    }
+}
\ No newline at end of file
diff --git a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/RequesterConfigForm.java b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/RequesterConfigForm.java
new file mode 100644
index 00000000..c397a4c4
--- /dev/null
+++ b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/RequesterConfigForm.java
@@ -0,0 +1,213 @@
+package io.github.tcdl.msb.jmeter.sampler.gui;
+
+import io.github.tcdl.msb.jmeter.sampler.RequesterConfig;
+import io.github.tcdl.msb.jmeter.sampler.gui.validation.IntegerVerifier;
+import io.github.tcdl.msb.jmeter.sampler.gui.validation.JsonVerifier;
+import io.github.tcdl.msb.jmeter.sampler.gui.validation.NotBlankVerifier;
+import io.github.tcdl.msb.jmeter.sampler.gui.validation.PatternVerifier;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+/**
+ * Created by rdro-tc on 28.07.16.
+ */
+public class RequesterConfigForm {
+
+    private JPanel configPanel;
+    private JPanel brokerConfigPanel;
+    private JPanel requesterConfigPanel;
+    private JScrollPane requestPayloadPanel;
+
+    private JLabel hostLabel;
+    private JLabel portLabel;
+    private JLabel virtualHostLabel;
+    private JLabel userNameLabel;
+    private JLabel passwordLabel;
+    private JTextField hostField;
+    private JTextField portField;
+    private JTextField virtualHostField;
+    private JTextField userNameField;
+    private JTextField passwordField;
+
+    private JLabel namespaceLabel;
+    private JLabel forwardNamespaceLabel;
+    private JLabel numberOfResponsesLabel;
+    private JLabel timeoutLabel;
+    private JTextField namespaceField;
+    private JTextField forwardNamespaceField;
+    private JTextField numberOfResponsesField;
+    private JTextField timeoutField;
+    private JCheckBox waitForResponsesCheckBox;
+
+    private JTextArea requestPayloadField;
+
+    public RequesterConfigForm(RequesterConfig config) {
+        setupUI();
+        setConfig(config);
+        waitForResponsesCheckBox.addItemListener(new ItemListener() {
+            public void itemStateChanged(ItemEvent e) {
+                onResponseEnabled(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+
+        hostField.setInputVerifier(new NotBlankVerifier());
+        portField.setInputVerifier(new IntegerVerifier(true));
+        virtualHostField.setInputVerifier(new NotBlankVerifier());
+        userNameField.setInputVerifier(new NotBlankVerifier());
+        passwordField.setInputVerifier(new NotBlankVerifier());
+
+        namespaceField.setInputVerifier(new PatternVerifier(true, "^_?([a-z0-9\\-]+\\:)+([a-z0-9\\-]+)$"));
+        forwardNamespaceField.setInputVerifier(new PatternVerifier(false, "^_?([a-z0-9\\-]+\\:)+([a-z0-9\\-]+)$"));
+        numberOfResponsesField.setInputVerifier(new IntegerVerifier(true));
+        timeoutField.setInputVerifier(new IntegerVerifier(true));
+        requestPayloadField.setInputVerifier(new JsonVerifier(true));
+    }
+
+    public JComponent getUIComponent() {
+        return configPanel;
+    }
+
+    private void onResponseEnabled(boolean enabled) {
+        numberOfResponsesField.setEnabled(enabled);
+        timeoutField.setEnabled(enabled);
+    }
+
+    public void setConfig(RequesterConfig config) {
+        hostField.setText(config.getHost());
+        portField.setText(config.getPort() != null ? config.getPort().toString() : "");
+        virtualHostField.setText(config.getVirtualHost());
+        userNameField.setText(config.getUserName());
+        passwordField.setText(config.getPassword());
+
+        namespaceField.setText(config.getNamespace());
+        forwardNamespaceField.setText(config.getForwardNamespace());
+        numberOfResponsesField.setText(config.getNumberOfResponses() != null ? config.getNumberOfResponses().toString() : "");
+        timeoutField.setText(config.getTimeout() != null ? config.getTimeout().toString() : "");
+        requestPayloadField.setText(config.getRequestPayload());
+        waitForResponsesCheckBox.setSelected(config.getWaitForResponses());
+    }
+
+    public RequesterConfig getConfig() {
+        RequesterConfig config = new RequesterConfig();
+
+        config.setHost(hostField.getText());
+        config.setPort(Integer.valueOf(portField.getText()));
+        config.setVirtualHost(virtualHostField.getText());
+        config.setUserName(userNameField.getText());
+        config.setPassword(passwordField.getText());
+
+        config.setNamespace(namespaceField.getText());
+        config.setForwardNamespace(forwardNamespaceField.getText());
+        config.setTimeout(Integer.valueOf(timeoutField.getText()));
+        config.setRequestPayload(requestPayloadField.getText());
+        config.setNumberOfResponses(Integer.valueOf(numberOfResponsesField.getText()));
+        config.setWaitForResponses(waitForResponsesCheckBox.isSelected());
+
+        return config;
+    }
+
+    private void setupUI() {
+        configPanel = new JPanel();
+        configPanel.setLayout(new GridBagLayout());
+
+        brokerConfigPanel = new JPanel();
+        brokerConfigPanel.setLayout(new GridBagLayout());
+        brokerConfigPanel.setBorder(BorderFactory.createTitledBorder("Broker configuration"));
+
+        hostLabel = new JLabel();
+        hostLabel.setText("host");
+        brokerConfigPanel.add(hostLabel, constraints(0, 0));
+        hostField = new JTextField();
+        hostField.setName("host");
+        brokerConfigPanel.add(hostField, constraints(1, 0));
+
+        portLabel = new JLabel();
+        portLabel.setText("port");
+        brokerConfigPanel.add(portLabel, constraints(0, 1));
+        portField = new JTextField();
+        portField.setName("port");
+        brokerConfigPanel.add(portField, constraints(1, 1));
+
+        virtualHostLabel = new JLabel();
+        virtualHostLabel.setText("virtual host");
+        brokerConfigPanel.add(virtualHostLabel, constraints(0, 2));
+        virtualHostField = new JTextField();
+        virtualHostField.setName("virtual host");
+        brokerConfigPanel.add(virtualHostField, constraints(1, 2));
+
+        userNameLabel = new JLabel();
+        userNameLabel.setText("user name");
+        brokerConfigPanel.add(userNameLabel, constraints(0, 3));
+        userNameField = new JTextField();
+        userNameField.setName("user name");
+        brokerConfigPanel.add(userNameField, constraints(1, 3));
+
+        passwordLabel = new JLabel();
+        passwordLabel.setText("password");
+        passwordLabel.setName("password");
+        brokerConfigPanel.add(passwordLabel, constraints(0, 4));
+        passwordField = new JTextField();
+        passwordField.setName("password");
+        brokerConfigPanel.add(passwordField, constraints(1, 4));
+        configPanel.add(brokerConfigPanel, constraints(0, 0));
+
+        requesterConfigPanel = new JPanel();
+        requesterConfigPanel.setLayout(new GridBagLayout());
+        requesterConfigPanel.setBorder(BorderFactory.createTitledBorder("Requester configuration"));
+
+        namespaceLabel = new JLabel("namespace");
+        requesterConfigPanel.add(namespaceLabel, constraints(0, 0));
+        namespaceField = new JTextField();
+        namespaceField.setName("namespace");
+        requesterConfigPanel.add(namespaceField, constraints(1, 0));
+
+        forwardNamespaceLabel = new JLabel("forward namespace");
+        requesterConfigPanel.add(forwardNamespaceLabel, constraints(0, 1));
+        forwardNamespaceField = new JTextField();
+        forwardNamespaceField.setName("forward namespace");
+        requesterConfigPanel.add(forwardNamespaceField, constraints(1, 1));
+
+        numberOfResponsesLabel = new JLabel();
+        numberOfResponsesLabel.setText("number of responses");
+        requesterConfigPanel.add(numberOfResponsesLabel, constraints(0, 2));
+        numberOfResponsesField = new JTextField();
+        numberOfResponsesField.setName("number of responses");
+        requesterConfigPanel.add(numberOfResponsesField, constraints(1, 2));
+
+        timeoutLabel = new JLabel();
+        timeoutLabel.setText("timeout, ms");
+        requesterConfigPanel.add(timeoutLabel, constraints(0, 3));
+        timeoutField = new JTextField();
+        timeoutField.setName("timeout");
+        requesterConfigPanel.add(timeoutField, constraints(1, 3));
+
+        waitForResponsesCheckBox = new JCheckBox();
+        waitForResponsesCheckBox.setEnabled(true);
+        waitForResponsesCheckBox.setSelected(true);
+        waitForResponsesCheckBox.setText("wait for responses");
+        requesterConfigPanel.add(waitForResponsesCheckBox, constraints(0, 4));
+        configPanel.add(requesterConfigPanel, constraints(0, 1));
+
+        requestPayloadPanel = new JScrollPane();
+        configPanel.add(requestPayloadPanel, constraints(0, 2));
+        requestPayloadPanel.setBorder(BorderFactory.createTitledBorder("Request payload"));
+        requestPayloadPanel.setPreferredSize(new Dimension(500, 300));
+        requestPayloadField = new JTextArea();
+        requestPayloadField.setName("request payload");
+        requestPayloadPanel.setViewportView(requestPayloadField);
+    }
+
+    private GridBagConstraints constraints(int gridx, int gridy) {
+        GridBagConstraints c = new GridBagConstraints();
+        c.fill = GridBagConstraints.HORIZONTAL;
+        c.anchor = GridBagConstraints.LINE_START;
+        c.gridx = gridx;
+        c.gridy = gridy;
+        c.weightx = 1;
+        c.weighty = 1;
+        return c;
+    }
+}
diff --git a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/IntegerVerifier.java b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/IntegerVerifier.java
new file mode 100644
index 00000000..46026bc6
--- /dev/null
+++ b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/IntegerVerifier.java
@@ -0,0 +1,39 @@
+package io.github.tcdl.msb.jmeter.sampler.gui.validation;
+
+import org.apache.commons.lang3.StringUtils;
+
+import javax.swing.*;
+
+/**
+ * Created by rdro-tc on 28.07.16.
+ */
+public class IntegerVerifier extends InputVerifier {
+
+    private boolean required = false;
+
+    public IntegerVerifier(boolean required) {
+        this.required = required;
+    }
+
+    public boolean verify(JComponent input) {
+        JTextField textField = (JTextField) input;
+
+        if (required && StringUtils.isBlank(textField.getText())) {
+            JOptionPane.showMessageDialog(input,
+                    "Required field: " + textField.getName(), "Validation Error",
+                    JOptionPane.ERROR_MESSAGE);
+            return false;
+        }
+
+        try {
+            Integer.valueOf(textField.getText());
+        } catch (NumberFormatException e) {
+            JOptionPane.showMessageDialog(input,
+                    "Invalid number: " + textField.getText(), "Validation Error",
+                    JOptionPane.ERROR_MESSAGE);
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/JsonVerifier.java b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/JsonVerifier.java
new file mode 100644
index 00000000..609fccb2
--- /dev/null
+++ b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/JsonVerifier.java
@@ -0,0 +1,51 @@
+package io.github.tcdl.msb.jmeter.sampler.gui.validation;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import io.github.tcdl.msb.api.exception.JsonConversionException;
+import io.github.tcdl.msb.support.Utils;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.swing.*;
+import java.util.Map;
+
+/**
+ * Created by rdro-tc on 02.08.16.
+ */
+public class JsonVerifier extends InputVerifier {
+
+    private ObjectMapper objectMapper = new ObjectMapper()
+            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
+            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+            .setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+    private boolean required = false;
+
+    public JsonVerifier(boolean required) {
+        this.required = required;
+    }
+
+    public boolean verify(JComponent input) {
+        JTextArea textField = (JTextArea) input;
+
+        if (required && StringUtils.isBlank(textField.getText())) {
+            JOptionPane.showMessageDialog(input,
+                    "Required field: " + textField.getName(), "Validation Error",
+                    JOptionPane.ERROR_MESSAGE);
+            return false;
+        }
+
+        try {
+            Utils.fromJson(textField.getText(), Map.class, objectMapper);
+        } catch (JsonConversionException e) {
+            JOptionPane.showMessageDialog(input,
+                    "Invalid json: " + textField.getText(),  "Validation Error",
+                    JOptionPane.ERROR_MESSAGE);
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/NotBlankVerifier.java b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/NotBlankVerifier.java
new file mode 100644
index 00000000..ade6d233
--- /dev/null
+++ b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/NotBlankVerifier.java
@@ -0,0 +1,25 @@
+package io.github.tcdl.msb.jmeter.sampler.gui.validation;
+
+import org.apache.commons.lang3.StringUtils;
+
+import javax.swing.*;
+
+/**
+ * Created by rdro-tc on 02.08.16.
+ */
+public class NotBlankVerifier extends InputVerifier {
+
+    public boolean verify(JComponent input) {
+        JTextField textField = (JTextField) input;
+
+        if (StringUtils.isBlank(textField.getText())) {
+            JOptionPane.showMessageDialog(input,
+                    "Required field: " + textField.getName(), "Validation Error",
+                    JOptionPane.ERROR_MESSAGE);
+
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/PatternVerifier.java b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/PatternVerifier.java
new file mode 100644
index 00000000..a7f87b17
--- /dev/null
+++ b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/gui/validation/PatternVerifier.java
@@ -0,0 +1,42 @@
+package io.github.tcdl.msb.jmeter.sampler.gui.validation;
+
+import org.apache.commons.lang3.StringUtils;
+
+import javax.swing.*;
+import java.util.regex.Pattern;
+
+/**
+ * Created by rdro-tc on 02.08.16.
+ */
+public class PatternVerifier extends IntegerVerifier {
+
+    private boolean required = false;
+    private Pattern pattern;
+
+    public PatternVerifier(boolean required, String pattern) {
+        super(required);
+        this.pattern = Pattern.compile(pattern);
+    }
+
+    public boolean verify(JComponent input) {
+        JTextField textField = (JTextField) input;
+
+        if (required && StringUtils.isBlank(textField.getText())) {
+            JOptionPane.showMessageDialog(input,
+                    "Required field: " + textField.getName(), "Validation Error",
+                    JOptionPane.ERROR_MESSAGE);
+
+            return false;
+        }
+
+        if (StringUtils.isNotBlank(textField.getText()) && !pattern.matcher(textField.getText()).matches()) {
+            JOptionPane.showMessageDialog(input,
+                    "Invalid " + textField.getName(), "Validation Error",
+                    JOptionPane.ERROR_MESSAGE);
+
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/jmeter/src/main/resources/application.conf b/jmeter/src/main/resources/application.conf
new file mode 100644
index 00000000..d6d15d37
--- /dev/null
+++ b/jmeter/src/main/resources/application.conf
@@ -0,0 +1,20 @@
+msbConfig {
+
+  serviceDetails = {
+    name = "jmeter"
+    version = "1.0.0"
+  }
+
+  brokerAdapterFactory = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory"
+
+  threadingConfig = {
+    consumerThreadPoolSize = 2
+  }
+
+  validateMessage = false
+
+  brokerConfig = {
+    durable = false
+  }
+
+}
\ No newline at end of file
diff --git a/jmeter/src/main/resources/logback.xml b/jmeter/src/main/resources/logback.xml
new file mode 100644
index 00000000..bff65f54
--- /dev/null
+++ b/jmeter/src/main/resources/logback.xml
@@ -0,0 +1,27 @@
+
+
+    
+        true
+        
+            UTF-8
+            %yellow(%d{HH:mm:ss.SSS})  %highlight(%-5level) %blue(%c{1}) %blue([%t]) %green(tags:%X{msbTags}) - %m%n
+        
+    
+
+    
+        logs/micro-services.log
+        
+            UTF-8
+            %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %c{1} [%t] tags:%X{msbTags} - %m%n
+        
+    
+
+    
+    
+
+    
+        
+        
+        
+    
+
\ No newline at end of file
diff --git a/jmeter/src/test/java/io/github/tcdl/msb/jmeter/sampler/UiTest.java b/jmeter/src/test/java/io/github/tcdl/msb/jmeter/sampler/UiTest.java
new file mode 100644
index 00000000..5f582a43
--- /dev/null
+++ b/jmeter/src/test/java/io/github/tcdl/msb/jmeter/sampler/UiTest.java
@@ -0,0 +1,18 @@
+package io.github.tcdl.msb.jmeter.sampler;
+
+import io.github.tcdl.msb.jmeter.sampler.gui.RequesterConfigForm;
+
+import javax.swing.*;
+
+/**
+ * Created by rdro-tc on 28.07.16.
+ */
+public class UiTest {
+
+    public static void main(String[] args) {
+        JFrame frame = new JFrame("Test");
+        frame.setContentPane(new RequesterConfigForm(new RequesterConfig()).getUIComponent());
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/jmeter/src/test/resources/examples/MSB Requester Sampler.jmx b/jmeter/src/test/resources/examples/MSB Requester Sampler.jmx
new file mode 100644
index 00000000..ee9a681c
--- /dev/null
+++ b/jmeter/src/test/resources/examples/MSB Requester Sampler.jmx	
@@ -0,0 +1,90 @@
+
+
+  
+    
+      
+      false
+      false
+      
+        
+      
+      
+    
+    
+      
+        continue
+        
+          false
+          1
+        
+        1
+        1
+        1470142254000
+        1470142254000
+        false
+        
+        
+      
+      
+        
+          true
+          100
+        
+        
+          
+            
+              TestElement.msb_requester
+              
+                localhost
+                5672
+                /
+                guest
+                guest
+                jmeter:test
+                true
+                1
+                3000
+                {}
+              
+            
+          
+          
+        
+      
+      
+        false
+        
+          saveConfig
+          
+            
+            true
+            true
+            true
+            
+            true
+            true
+            true
+            true
+            false
+            true
+            true
+            false
+            false
+            false
+            true
+            false
+            false
+            false
+            true
+            0
+            true
+            true
+            true
+          
+        
+        
+      
+      
+    
+  
+
diff --git a/jmeter/SearchForYear_100Users.jmx b/jmeter/src/test/resources/examples/SearchForYear_100Users.jmx
similarity index 100%
rename from jmeter/SearchForYear_100Users.jmx
rename to jmeter/src/test/resources/examples/SearchForYear_100Users.jmx
diff --git a/jmeter/SearchForYear_10Users.jmx b/jmeter/src/test/resources/examples/SearchForYear_10Users.jmx
similarity index 100%
rename from jmeter/SearchForYear_10Users.jmx
rename to jmeter/src/test/resources/examples/SearchForYear_10Users.jmx
diff --git a/pom.xml b/pom.xml
index f72740c0..5eb92f35 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
         amqp
         cli
         acceptance
+        jmeter
         examples
     
 

From 5a5cc42d5f9d75f9f18868191ff38fef76c5b303 Mon Sep 17 00:00:00 2001
From: rdro-tc 
Date: Wed, 3 Aug 2016 18:02:45 +0300
Subject: [PATCH 144/226] Improved error response for jmeter plugin

---
 jmeter/pom.xml                                |  2 +-
 .../jmeter/sampler/MsbRequesterSampler.java   |  7 ++++--
 .../msb/jmeter/sampler/RequesterConfig.java   | 25 +++++++++++++++++++
 3 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index eb71c28b..1a78488f 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -69,7 +69,7 @@
                         
                         
                             ${project.artifactId}-${project.version}
-                            true
+                            false
                         
                     
                 
diff --git a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/MsbRequesterSampler.java b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/MsbRequesterSampler.java
index 3995dfd4..9237c5f4 100644
--- a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/MsbRequesterSampler.java
+++ b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/MsbRequesterSampler.java
@@ -1,4 +1,3 @@
-
 package io.github.tcdl.msb.jmeter.sampler;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
@@ -34,6 +33,7 @@ public class MsbRequesterSampler extends AbstractSampler {
 
     private String MSB_BROKER_CONFIG_ROOT = "msbConfig.brokerConfig";
 
+    private RequesterConfig currentRequesterConfig;
     private MsbContext msbContext;
 
     private ObjectMapper objectMapper = new ObjectMapper()
@@ -68,7 +68,8 @@ public SampleResult sample(Entry e) {
         trace("sample()");
 
         RequesterConfig requesterConfig = (RequesterConfig)getProperty(RequesterConfig.TEST_ELEMENT_CONFIG).getObjectValue();
-        if (msbContext == null) {
+        if (msbContext == null || !requesterConfig.equals(currentRequesterConfig)) {
+            currentRequesterConfig = requesterConfig;
             initMsb(requesterConfig);
         }
 
@@ -126,6 +127,8 @@ public SampleResult sample(Entry e) {
             res.setResponseData(responsesAsJson, null);
         } else {
             res.setSuccessful(false);
+            res.setResponseCode("500");
+            res.setResponseMessage("No response(s)");
         }
 
         return res;
diff --git a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/RequesterConfig.java b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/RequesterConfig.java
index 2648c138..b73edd10 100644
--- a/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/RequesterConfig.java
+++ b/jmeter/src/main/java/io/github/tcdl/msb/jmeter/sampler/RequesterConfig.java
@@ -1,5 +1,7 @@
 package io.github.tcdl.msb.jmeter.sampler;
 
+import java.util.Objects;
+
 import static io.github.tcdl.msb.support.Utils.ifNull;
 import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
 
@@ -126,4 +128,27 @@ public void setDefaults() {
         this.timeout =  this.timeout == null || this.timeout <= 0 ? 3000 : this.timeout;
         this.requestPayload = defaultIfBlank(this.requestPayload, "{}");
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        RequesterConfig that = (RequesterConfig) o;
+        return Objects.equals(host, that.host) &&
+                Objects.equals(port, that.port) &&
+                Objects.equals(virtualHost, that.virtualHost) &&
+                Objects.equals(userName, that.userName) &&
+                Objects.equals(password, that.password) &&
+                Objects.equals(namespace, that.namespace) &&
+                Objects.equals(forwardNamespace, that.forwardNamespace) &&
+                Objects.equals(waitForResponses, that.waitForResponses) &&
+                Objects.equals(numberOfResponses, that.numberOfResponses) &&
+                Objects.equals(timeout, that.timeout) &&
+                Objects.equals(requestPayload, that.requestPayload);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(host, port, virtualHost, userName, password, namespace, forwardNamespace, waitForResponses, numberOfResponses, timeout, requestPayload);
+    }
 }

From ff3b15d73453a60921c752d38a761dd5ddd0807b Mon Sep 17 00:00:00 2001
From: alex 
Date: Mon, 29 Aug 2016 10:49:24 +0300
Subject: [PATCH 145/226] routing keys support

---
 .../bdd/steps/RequesterResponderSteps.java    |  51 +++++++-
 .../resources/scenarios/routing_keys.story    |   7 ++
 amqp/pom.xml                                  |   5 +
 .../msb/adapters/amqp/AmqpAdapterFactory.java |  14 +++
 .../adapters/amqp/AmqpConsumerAdapter.java    |  57 ++++++---
 .../adapters/amqp/AmqpProducerAdapter.java    |  25 +++-
 .../amqp/AmqpConsumerAdapterTest.java         |  79 +++++++++----
 .../amqp/AmqpProducerAdapterTest.java         |  14 ++-
 .../io/github/tcdl/msb/ChannelManager.java    | 111 +++++++++++++++---
 .../java/io/github/tcdl/msb/Producer.java     |   7 +-
 .../tcdl/msb/adapters/AdapterFactory.java     |  20 ++++
 .../tcdl/msb/adapters/ProducerAdapter.java    |   8 ++
 .../tcdl/msb/api/MessageDestination.java      |  51 ++++++++
 .../io/github/tcdl/msb/api/ObjectFactory.java |  63 ++++++++++
 .../github/tcdl/msb/api/RequestOptions.java   |  32 ++++-
 .../tcdl/msb/impl/ObjectFactoryImpl.java      |  46 ++++++--
 .../github/tcdl/msb/impl/RequesterImpl.java   |  18 ++-
 .../tcdl/msb/impl/ResponderServerImpl.java    |  38 +++---
 .../github/tcdl/msb/ChannelManagerTest.java   |  79 +++++++++++--
 .../java/io/github/tcdl/msb/ProducerTest.java |  12 +-
 .../github/tcdl/msb/api/ChannelMonitorIT.java |  11 +-
 .../tcdl/msb/api/RequestOptionsTest.java      |   3 +
 .../tcdl/msb/api/RequesterResponderIT.java    |   3 +-
 .../tcdl/msb/impl/RequesterImplTest.java      |   1 +
 .../msb/impl/ResponderServerImplTest.java     |  51 ++++++--
 .../adapterfactory/TestMsbAdapterFactory.java |  19 +++
 .../TestMsbProducerAdapter.java               |  13 +-
 .../TestMsbStorageForAdapterFactory.java      |  90 ++++++++------
 .../mock/objectfactory/ResponderCapture.java  |  18 +++
 .../objectfactory/TestMsbObjectFactory.java   |  20 ++++
 .../TestMsbStorageForObjectFactory.java       |   3 +
 pom.xml                                       |   5 +
 32 files changed, 814 insertions(+), 160 deletions(-)
 create mode 100644 acceptance/src/test/resources/scenarios/routing_keys.story
 create mode 100644 core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java

diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 2dabaa6c..003c23ce 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -1,9 +1,16 @@
 package io.github.tcdl.msb.acceptance.bdd.steps;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigValueFactory;
+import io.github.tcdl.msb.api.MessageDestination;
+import io.github.tcdl.msb.api.MessageTemplate;
+import io.github.tcdl.msb.api.MsbContext;
 import io.github.tcdl.msb.api.Requester;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.support.Utils;
+import org.apache.commons.collections.CollectionUtils;
 import org.hamcrest.Matchers;
 import org.jbehave.core.annotations.Given;
 import org.jbehave.core.annotations.Then;
@@ -12,14 +19,13 @@
 import org.jbehave.core.model.OutcomesTable;
 import org.junit.Assert;
 
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 /**
@@ -47,6 +53,8 @@ public class RequesterResponderSteps extends MsbSteps {
     private final String ACK = "ACK";
     private final String PAYLOAD = "PAYLOAD";
     private final int ACK_TIMEOUT = 500;
+    private final Map> receivedMessagesByConsumer = new ConcurrentHashMap<>();
+
 
     public Optional getDefaultRequestsAckType() {
         return defaultRequestsAckType;
@@ -235,13 +243,38 @@ public void sendRequestWithQuery(String query) throws Exception {
         helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse, this::onEnd);
     }
 
-    @When("requester sends a request with body '$body'")
+    @When("^requester sends a request with body '$body'$")
     public void sendRequestWithBody(String body) throws Exception {
         onBeforeRequest();
         RestPayload payload = helper.createFacetParserPayload(null, body);
         helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse, this::onEnd);
     }
 
+    @Given("$responderId responder server listens on namespace $namespace with routing keys $routingKeys")
+    public void subscribeResponder(String responderId, String namespace, List routingKeys) {
+        //modify name to make library generate different queue names for different consumers (responders)
+        Config config = ConfigFactory.load()
+                .withValue("msbConfig.serviceDetails.name", ConfigValueFactory.fromAnyRef("msb_java_" + responderId));
+
+        helper.initWithConfig(responderId, config);
+        MsbContext context = helper.getContext(responderId);
+        context.getObjectFactory().createResponderServer(namespace, new HashSet<>(routingKeys), new MessageTemplate(),
+                (request, responderContext) -> {
+                    receivedMessagesByConsumer.computeIfAbsent(responderId, key -> new LinkedList<>()).add(request);
+                }, String.class).listen();
+    }
+
+    @Then("$responderId responder receives only messages $messages")
+    public void assertReceivedMessages(String responderId, List expectedMessagesRaw) throws InterruptedException {
+
+        List expectedMessages = expectedMessagesRaw.stream()
+                .map(message -> message.substring(1, message.length() - 1)) //remove surrounding ' symbols
+                .collect(Collectors.toList());
+        TimeUnit.SECONDS.sleep(1); //wait until messages will be delivered
+        List capturedMessages = receivedMessagesByConsumer.get(responderId);
+        assertTrue(CollectionUtils.isEqualCollection(expectedMessages, capturedMessages));
+    }
+
     private void onBeforeRequest() {
         receivedResponse = null;
         receivedResponseFuture = new CompletableFuture<>();
@@ -345,6 +378,14 @@ public void requestForSingleResult(String namespace) throws Exception {
         lastFutureResult = helper.sendForResult(requester, payload);
     }
 
+    @When("requester sends to $namespace a request with body '$body' and routing key $routingKey")
+    public void requestForSingleResult(String namespace, String body, String routingKey) throws Exception {
+        helper.initDefault();
+        helper.getContext(DEFAULT_CONTEXT_NAME).getObjectFactory()
+                .createRequesterForFireAndForget(new MessageDestination(namespace, routingKey), new MessageTemplate())
+                .publish(body);
+    }
+
     @When("requester blocks waiting for response for $timeout ms")
     public void blockUntilResponseReceived(int timeout) throws Exception {
         resolvedResponse = lastFutureResult.get(timeout, TimeUnit.MILLISECONDS);
diff --git a/acceptance/src/test/resources/scenarios/routing_keys.story b/acceptance/src/test/resources/scenarios/routing_keys.story
new file mode 100644
index 00000000..14ca6ad5
--- /dev/null
+++ b/acceptance/src/test/resources/scenarios/routing_keys.story
@@ -0,0 +1,7 @@
+Given 1st responder server listens on namespace test:namespace with routing keys routing-key-1, routing-key-2
+Given 2nd responder server listens on namespace test:namespace with routing keys routing-key-3
+When requester sends to test:namespace a request with body '{"messageId":"rk1"}' and routing key routing-key-1
+When requester sends to test:namespace a request with body '{"messageId":"rk2"}' and routing key routing-key-2
+When requester sends to test:namespace a request with body '{"messageId":"rk3"}' and routing key routing-key-3
+Then 1st responder receives only messages '{"messageId":"rk1"}', '{"messageId":"rk2"}'
+Then 2nd responder receives only messages '{"messageId":"rk3"}'
\ No newline at end of file
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 8e226126..a96f129e 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -36,5 +36,10 @@
             com.rabbitmq
             amqp-client
         
+        
+            commons-collections
+            commons-collections
+            test
+        
     
 
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
index e740ea64..b9b62c74 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
@@ -8,6 +8,7 @@
 import io.github.tcdl.msb.adapters.AdapterFactory;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
+import io.github.tcdl.msb.api.MessageDestination;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
 import io.github.tcdl.msb.config.MsbConfig;
@@ -16,7 +17,10 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.*;
 
 /**
@@ -61,11 +65,21 @@ public ProducerAdapter createProducerAdapter(String topic) {
         return new AmqpProducerAdapter(topic, amqpBrokerConfig, connectionManager);
     }
 
+    @Override
+    public ProducerAdapter createProducerAdapter(MessageDestination destination) {
+        return new AmqpProducerAdapter(destination, amqpBrokerConfig, connectionManager);
+    }
+
     @Override
     public ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
         return new AmqpConsumerAdapter(topic, amqpBrokerConfig, connectionManager, isResponseTopic);
     }
 
+    @Override
+    public ConsumerAdapter createConsumerAdapter(String topic, Set routingKeys) {
+        return new AmqpConsumerAdapter(topic, routingKeys, amqpBrokerConfig, connectionManager);
+    }
+
     protected ConnectionFactory createConnectionFactory(AmqpBrokerConfig adapterConfig) {
         String host = adapterConfig.getHost();
         int port = adapterConfig.getPort();
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
index 3a575340..0a0788e0 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
@@ -1,41 +1,62 @@
 package io.github.tcdl.msb.adapters.amqp;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
 
+import com.google.common.collect.Lists;
+import com.rabbitmq.client.AMQP;
 import com.rabbitmq.client.Channel;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.api.MessageDestination;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import io.github.tcdl.msb.support.Utils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class AmqpConsumerAdapter implements ConsumerAdapter {
 
-    private String topic;
     private Channel channel;
-    private String exchangeName;
+    private final String exchangeName;
+    private final Set routingKeys;
     private String consumerTag;
     private AmqpBrokerConfig adapterConfig;
     private boolean isResponseTopic = false;
 
     /**
      * The constructor.
-     * @param topic - a topic name associated with the adapter
-     * @throws ChannelException if some problems during setup channel from RabbitMQ connection were occurred
+     *
+     * @param exchangeName - an exchange name associated with the adapter
+     * @throws ChannelException if adapter failed to create channel and declare exchange
      */
-    public AmqpConsumerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager, boolean isResponseTopic) {
-        Validate.notNull(topic, "the 'topic' must not be null");
+    public AmqpConsumerAdapter(String exchangeName, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager, boolean isResponseTopic) {
+        this(exchangeName, "fanout", Collections.singleton(StringUtils.EMPTY), amqpBrokerConfig, connectionManager, isResponseTopic);
+    }
+
+    public AmqpConsumerAdapter(String topic, Set routingKeys, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
+        this(topic, "topic", routingKeys, amqpBrokerConfig, connectionManager, false);
+    }
 
-        this.topic = topic;
-        this.exchangeName = topic;
+    private AmqpConsumerAdapter(String exchangeName, String exchangeType, Set routingKeys, AmqpBrokerConfig amqpBrokerConfig,
+                                AmqpConnectionManager connectionManager, boolean isResponseTopic) {
+
+        Validate.notNull(exchangeName, "Exchange name is required");
+        Validate.notEmpty(routingKeys, "At least one routing key is required");
+
+        this.routingKeys = routingKeys;
+        this.exchangeName = exchangeName;
         this.adapterConfig = amqpBrokerConfig;
         this.isResponseTopic = isResponseTopic;
 
         try {
             channel = connectionManager.obtainConnection().createChannel();
-            channel.exchangeDeclare(exchangeName, "fanout", false /* durable */, true /* auto-delete */, null);
+            channel.exchangeDeclare(exchangeName, exchangeType, false /* durable */, true /* auto-delete */, null);
         } catch (IOException e) {
-            throw new ChannelException("Failed to setup channel from ActiveMQ connection", e);
+            throw new ChannelException("Failed to setup channel", e);
         }
     }
 
@@ -48,21 +69,22 @@ public void subscribe(RawMessageHandler msgHandler) {
         boolean durable = isDurable();
         int prefetchCount = adapterConfig.getPrefetchCount();
 
-        String queueName = generateQueueName(topic, groupId, durable);
+        String queueName = generateQueueName(exchangeName, groupId, durable);
 
         try {
             channel.queueDeclare(queueName, durable /* durable */, false /* exclusive */, !durable /*auto-delete */, null);
             channel.basicQos(prefetchCount); // Don't accept more messages if we have any unacknowledged
-            channel.queueBind(queueName, exchangeName, "");
-
+            for(String routingKey: routingKeys) {
+                channel.queueBind(queueName, exchangeName, routingKey);
+            }
             consumerTag = channel.basicConsume(queueName, false /* autoAck */, new AmqpMessageConsumer(channel, msgHandler, adapterConfig));
         } catch (IOException e) {
-            throw new ChannelException(String.format("Failed to subscribe to topic %s", topic), e);
+            throw new ChannelException(String.format("Failed to subscribe to topic %s with routing keys %s", exchangeName, routingKeys), e);
         }
     }
 
     protected boolean isDurable() {
-        if(isResponseTopic) {
+        if (isResponseTopic) {
             //response topic is always auto-delete and not durable
             return false;
         }
@@ -77,13 +99,14 @@ public void unsubscribe() {
         try {
             channel.basicCancel(consumerTag);
         } catch (IOException e) {
-            throw new ChannelException(String.format("Failed to unsubscribe from topic %s", topic), e);
+            throw new ChannelException(String.format("Failed to unsubscribe from topic %s", exchangeName), e);
         }
     }
 
     /**
      * Generate topic name to get unique topics for different microservices
-     * @param topic - topic name associated with the adapter
+     *
+     * @param topic   - topic name associated with the adapter
      * @param groupId - group service Id
      * @param durable - queue durability
      */
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
index 034474fa..39909b24 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
@@ -2,8 +2,10 @@
 
 import com.rabbitmq.client.MessageProperties;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
+import io.github.tcdl.msb.api.MessageDestination;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
 
 import java.io.IOException;
@@ -20,6 +22,14 @@ public class AmqpProducerAdapter implements ProducerAdapter {
      * @throws ChannelException if some problems during setup channel from RabbitMQ connection were occurred
      */
     public AmqpProducerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
+        this(topic, "fanout", amqpBrokerConfig, connectionManager);
+    }
+
+    public AmqpProducerAdapter(MessageDestination destination, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
+        this(destination.getTopic(), "topic", amqpBrokerConfig, connectionManager);
+    }
+
+    public AmqpProducerAdapter(String topic, String exchangeType, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
         Validate.notNull(topic, "the 'topic' must not be null");
 
         this.exchangeName = topic;
@@ -27,7 +37,7 @@ public AmqpProducerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, Amqp
         this.amqpAutoRecoveringChannel = new AmqpAutoRecoveringChannel(connectionManager);
 
         try {
-            amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, "fanout", false /* durable */, true /* auto-delete */, null);
+            amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, exchangeType, false /* durable */, true /* auto-delete */, null);
         } catch (IOException e) {
             throw new ChannelException("Failed to setup channel from ActiveMQ connection", e);
         }
@@ -38,11 +48,18 @@ public AmqpProducerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, Amqp
      */
     @Override
     public void publish(String jsonMessage) {
+        publish(jsonMessage, StringUtils.EMPTY);
+    }
+
+    @Override
+    public void publish(String jsonMessage, String routingKey) {
+        Validate.notNull(routingKey, "routing key is required");
+        Charset charset = amqpBrokerConfig.getCharset();
+
         try {
-            Charset charset = amqpBrokerConfig.getCharset();
-            amqpAutoRecoveringChannel.basicPublish(exchangeName, "" /* routing key */, MessageProperties.PERSISTENT_BASIC, jsonMessage.getBytes(charset));
+            amqpAutoRecoveringChannel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_BASIC, jsonMessage.getBytes(charset));
         } catch (IOException e) {
-            throw new ChannelException(String.format("Failed to publish message '%s' into exchange '%s'", jsonMessage, exchangeName), e);
+            throw new ChannelException(String.format("Failed to publish message '%s' into exchange '%s' with routing key '%s'", jsonMessage, exchangeName, routingKey), e);
         }
     }
 }
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
index e3b4cd0e..013510b8 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
@@ -1,28 +1,29 @@
 package io.github.tcdl.msb.adapters.amqp;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
-
+import com.google.common.collect.Sets;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.Consumer;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
+import org.apache.commons.collections.CollectionUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 
 import java.io.IOException;
 import java.nio.charset.Charset;
+import java.util.Collections;
 import java.util.Optional;
+import java.util.Set;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.Consumer;
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
 
 public class AmqpConsumerAdapterTest {
 
@@ -41,9 +42,10 @@ public void setUp() throws Exception {
     }
 
     @Test
-    public void testTopicExchangeCreated() throws Exception {
+    public void testFanoutExchangeCreated() throws Exception {
         String topicName = "myTopic";
-        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf(topicName, "myGroupId", false);
+        String groupId = "groupId";
+        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf(topicName, groupId, false);
 
         adapter.subscribe((jsonMessage, ackHandler) -> {
         });
@@ -51,6 +53,17 @@ public void testTopicExchangeCreated() throws Exception {
         verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null);
     }
 
+    @Test
+    public void testTopicExchangeCreated() throws Exception {
+        String topicName = "myTopic";
+        String groupId = "groupId";
+        String routingKey = "routing-key";
+
+        new AmqpConsumerAdapter(topicName, Collections.singleton(routingKey), brokerConfig(groupId, true), mockAmqpConnectionManager);
+        verify(mockChannel).exchangeDeclare(topicName, "topic", false, true, null);
+
+    }
+
     @Test(expected = RuntimeException.class)
     public void testInitializationError() throws IOException {
         when(mockChannel.exchangeDeclare(anyString(), anyString(), anyBoolean(), anyBoolean(), any())).thenThrow(new IOException());
@@ -58,6 +71,22 @@ public void testInitializationError() throws IOException {
         createAdapterWithNonDurableConf("myTopic", "myGroupId", false);
     }
 
+    @Test
+    public void testSubscribeMultipleRoutingKeysMultipleBindings() throws Exception {
+        String topicName = "myTopic";
+        String groupId = "groupId";
+
+        Set routingKeys = Sets.newHashSet("routing-key-1", "routing-key-2");
+
+        AmqpConsumerAdapter amqpConsumerAdapter = new AmqpConsumerAdapter(topicName, routingKeys, brokerConfig(groupId, true), mockAmqpConnectionManager);
+        amqpConsumerAdapter.subscribe((jsonMessage, acknowledgementHandler) -> {});
+
+        ArgumentCaptor routingKeysCaptor = ArgumentCaptor.forClass(String.class);
+        verify(mockChannel, times(2)).queueBind(eq("myTopic.groupId.d"), eq(topicName), routingKeysCaptor.capture());
+
+        assertTrue(CollectionUtils.isEqualCollection(routingKeys, routingKeysCaptor.getAllValues()));
+    }
+
     @Test
     public void testSubscribeTransientQueueCreated() throws IOException {
         AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", false);
@@ -163,7 +192,7 @@ public void testIsDurableFalseIfResponseTopicAndNonDurableConfig() throws IOExce
 
         AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", isResponseTopic);
 
-        assertTrue(adapter.isDurable() == false);
+        assertFalse(adapter.isDurable());
     }
 
     @Test
@@ -172,7 +201,7 @@ public void testIsDurableFalseIfNotResponseTopicAndNonDurableConfig() throws IOE
 
         AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", isResponseTopic);
 
-        assertTrue(adapter.isDurable() == false);
+        assertFalse(adapter.isDurable());
     }
 
     @Test
@@ -181,7 +210,7 @@ public void testIsDurableFalseIfResponseTopicAndDurableConfig() throws IOExcepti
 
         AmqpConsumerAdapter adapter = createAdapterWithDurableConf("myTopic", "myGroupId", isResponseTopic);
 
-        assertTrue(adapter.isDurable() == false);
+        assertFalse(adapter.isDurable());
     }
 
     @Test
@@ -190,7 +219,7 @@ public void testIsDurableTrueIfNotResponseTopicAndDurableConfig() throws IOExcep
 
         AmqpConsumerAdapter adapter = createAdapterWithDurableConf("myTopic", "myGroupId", isResponseTopic);
 
-        assertTrue(adapter.isDurable() == true);
+        assertTrue(adapter.isDurable());
     }
 
     private AmqpConsumerAdapter createAdapterWithNonDurableConf(String topic, String groupId, boolean isResponseTopic) {
@@ -206,4 +235,12 @@ private AmqpConsumerAdapter createAdapterWithDurableConf(String topic, String gr
                 false, Optional.of(groupId), isDurableConf, 1, 5000, 1);
         return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
     }
+
+    private AmqpBrokerConfig brokerConfig(String groupId, boolean durable) {
+        return new AmqpBrokerConfig(
+                Charset.forName("UTF-8"),
+                "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
+                false, Optional.of(groupId), durable, 1, 5000, 1
+        );
+    }
 }
\ No newline at end of file
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java
index 7fb18aae..4bdaa162 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java
@@ -6,6 +6,7 @@
 import com.rabbitmq.client.MessageProperties;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
+import org.apache.commons.lang3.StringUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.AdditionalMatchers;
@@ -61,7 +62,18 @@ public void testPublish() throws ChannelException, IOException {
 
         producerAdapter.publish(message);
 
-        verify(mockChannel).basicPublish(topicName, "" /* routing key */, MessageProperties.PERSISTENT_BASIC, message.getBytes());
+        verify(mockChannel).basicPublish(topicName, StringUtils.EMPTY, MessageProperties.PERSISTENT_BASIC, message.getBytes());
+    }
+
+    @Test
+    public void testPublishWithRoutingKey() throws Exception{
+        String topicName = "myTopic";
+        String message = "message";
+        String routingKey = "routingKey";
+        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, mockAmqpBrokerConfig, mockAmqpConnectionManager);
+
+        producerAdapter.publish(message, routingKey);
+        verify(mockChannel).basicPublish(topicName, routingKey, MessageProperties.PERSISTENT_BASIC, message.getBytes());
     }
 
     @Test
diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
index 10fb52ad..4c03b282 100644
--- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
@@ -2,12 +2,15 @@
 
 import java.time.Clock;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.github.tcdl.msb.adapters.*;
 import io.github.tcdl.msb.api.Callback;
+import io.github.tcdl.msb.api.MessageDestination;
 import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException;
+import io.github.tcdl.msb.api.exception.MsbException;
 import io.github.tcdl.msb.collector.CollectorManager;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.api.message.Message;
@@ -37,8 +40,11 @@ public class ChannelManager {
     private final MessageHandlerInvoker messageHandlerInvoker;
     private ChannelMonitorAgent channelMonitorAgent;
 
-    private final Map producersByTopic;
-    private final Map consumersByTopic;
+    private final Map broadcastProducersByTopic;
+    private final Map multicastProducersByTopic;
+
+    private final Map broadcastConsumersByTopic;
+    private final Map multicastConsumersByTopic;
 
     public ChannelManager(MsbConfig msbConfig, Clock clock, JsonValidator validator, ObjectMapper messageMapper, AdapterFactory adapterFactory, MessageHandlerInvoker messageHandlerInvoker) {
         this.msbConfig = msbConfig;
@@ -47,15 +53,20 @@ public ChannelManager(MsbConfig msbConfig, Clock clock, JsonValidator validator,
         this.messageMapper = messageMapper;
         this.adapterFactory = adapterFactory;
         this.messageHandlerInvoker = messageHandlerInvoker;
-        this.producersByTopic = new ConcurrentHashMap<>();
-        this.consumersByTopic = new ConcurrentHashMap<>();
+        this.broadcastProducersByTopic = new ConcurrentHashMap<>();
+        this.multicastProducersByTopic = new ConcurrentHashMap<>();
+        this.broadcastConsumersByTopic = new ConcurrentHashMap<>();
+        this.multicastConsumersByTopic = new ConcurrentHashMap<>();
 
         channelMonitorAgent = new NoopChannelMonitorAgent();
     }
 
     public Producer findOrCreateProducer(final String topic) {
         Validate.notNull(topic, "field 'topic' is null");
-        Producer producer = producersByTopic.computeIfAbsent(topic, key -> {
+        if(multicastProducersByTopic.containsKey(topic)){
+            throw new MsbException("Producer for " + topic + " already exists for multicast mode.");
+        }
+        Producer producer = broadcastProducersByTopic.computeIfAbsent(topic, key -> {
             Producer newProducer = createProducer(key);
             channelMonitorAgent.producerTopicCreated(key);
             return newProducer;
@@ -64,24 +75,62 @@ public Producer findOrCreateProducer(final String topic) {
         return producer;
     }
 
+    public Producer findOrCreateProducer(MessageDestination destination) {
+        Validate.notNull(destination, "destination is mandatory");
+        String topic = destination.getTopic();
+        if(broadcastProducersByTopic.containsKey(topic)){
+            throw new MsbException("Producer for " + topic + " already exists for broadcast mode.");
+        }
+        Producer producer = multicastProducersByTopic.computeIfAbsent(topic, namespace -> {
+            Producer newProducer = createProducer(destination);
+            channelMonitorAgent.producerTopicCreated(namespace);
+            return newProducer;
+        });
+
+        return producer;
+    }
+
     /**
      * Start consuming messages on specified topic with handler.
      * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages.
      *
-     * @param topic
      * @param messageHandler handler for processing messages
      * @throws ConsumerSubscriptionException if subscriber for topic already exist
      */
-    public synchronized boolean subscribe(String topic, MessageHandler messageHandler) {
+    public synchronized void subscribe(String topic, Set routingKeys, MessageHandler messageHandler) {
+        Validate.notBlank(topic, "field 'topic' is empty");
+        Validate.notEmpty(routingKeys, "At least one routing key is required");
+
+        if(broadcastConsumersByTopic.get(topic) != null){
+            throw new ConsumerSubscriptionException("Consumer for " + topic + " already exists for broadcast mode.");
+        }
+
+        if (multicastConsumersByTopic.get(topic) != null) {
+            throw new ConsumerSubscriptionException("Subscriber for this topic " + topic + " already exist");
+        } else {
+            Consumer newConsumer = createConsumer(topic, routingKeys, new SimpleMessageHandlerResolverImpl(messageHandler, RESPONDER_LOGGING_NAME));
+            channelMonitorAgent.consumerTopicCreated(topic);
+            multicastConsumersByTopic.put(topic, newConsumer);
+        }
+    }
+
+    /**
+     * * Start consuming messages on specified topic with handler.
+     */
+    public void subscribe(String topic, MessageHandler messageHandler) {
         Validate.notNull(topic, "field 'topic' is null");
         Validate.notNull(messageHandler, "field 'messageHandler' is null");
-        if (consumersByTopic.get(topic) != null) {
-            throw new ConsumerSubscriptionException("Subscriber for this topic: " + topic + " already exist");
+
+        if(multicastConsumersByTopic.get(topic) != null){
+            throw new ConsumerSubscriptionException("Consumer for " + topic + " already exists for multicast mode.");
+        }
+
+        if (broadcastConsumersByTopic.get(topic) != null) {
+            throw new ConsumerSubscriptionException("Subscriber for this topic " + topic + " already exist");
         } else {
             Consumer newConsumer = createConsumer(topic, false, new SimpleMessageHandlerResolverImpl(messageHandler, RESPONDER_LOGGING_NAME));
             channelMonitorAgent.consumerTopicCreated(topic);
-            consumersByTopic.put(topic, newConsumer);
-            return false;
+            broadcastConsumersByTopic.put(topic, newConsumer);
         }
     }
 
@@ -96,12 +145,17 @@ public synchronized boolean subscribe(String topic, MessageHandler messageHandle
     public synchronized boolean subscribeForResponses(String topic, CollectorManager collectorManager) {
         Validate.notNull(topic, "field 'topic' is null");
         Validate.notNull(collectorManager, "field 'collectorManager' is null");
-        if (consumersByTopic.get(topic) != null) {
+
+        if(multicastConsumersByTopic.get(topic) != null){
+            throw new ConsumerSubscriptionException("Consumer for " + topic + " already exists for multicast mode.");
+        }
+
+        if (broadcastConsumersByTopic.get(topic) != null) {
             throw new ConsumerSubscriptionException("Subscriber for this topic: " + topic + " already exist");
         } else {
             Consumer newConsumer = createConsumer(topic, true, collectorManager);
+            broadcastConsumersByTopic.put(topic, newConsumer);
             channelMonitorAgent.consumerTopicCreated(topic);
-            consumersByTopic.put(topic, newConsumer);
             return false;
         }
     }
@@ -111,7 +165,16 @@ public synchronized boolean subscribeForResponses(String topic, CollectorManager
      * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages.
      */
     public synchronized void unsubscribe(String topic) {
-        Consumer consumer = consumersByTopic.remove(topic);
+        if(broadcastConsumersByTopic.get(topic) != null){
+            stopConsumer(topic, broadcastConsumersByTopic.remove(topic));
+        }
+
+        if(multicastConsumersByTopic.get(topic) != null){
+            stopConsumer(topic, multicastConsumersByTopic.remove(topic));
+        }
+    }
+
+    private void stopConsumer(String topic, Consumer consumer) {
         if (consumer != null) {
             consumer.end();
             channelMonitorAgent.consumerTopicRemoved(topic);
@@ -126,11 +189,27 @@ private Producer createProducer(String topic) {
         return new Producer(adapter, topic, handler, messageMapper);
     }
 
-    private Consumer createConsumer(String topic, boolean isResponseTopic, MessageHandlerResolver messageHandlerResolver) {
+    private Producer createProducer(MessageDestination destination) {
+        String topic = destination.getTopic();
         Utils.validateTopic(topic);
 
-        ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic, isResponseTopic );
+        ProducerAdapter adapter = getAdapterFactory().createProducerAdapter(destination);
+        Callback handler = message -> channelMonitorAgent.producerMessageSent(topic);
+        return new Producer(adapter, topic, handler, messageMapper);
+    }
+
+    private Consumer createConsumer(String topic, boolean isResponseTopic, MessageHandlerResolver messageHandlerResolver) {
+        ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic, isResponseTopic);
+        return createConsumer(topic, adapter, messageHandlerResolver);
+    }
 
+    private Consumer createConsumer(String topic, Set routingKeys, MessageHandlerResolver messageHandlerResolver) {
+        ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic, routingKeys);
+        return createConsumer(topic, adapter, messageHandlerResolver);
+    }
+
+    private Consumer createConsumer(String topic, ConsumerAdapter adapter, MessageHandlerResolver messageHandlerResolver){
+        Utils.validateTopic(topic);
         return new Consumer(adapter, messageHandlerInvoker, topic, messageHandlerResolver, msbConfig, clock, channelMonitorAgent, validator, messageMapper);
     }
 
diff --git a/core/src/main/java/io/github/tcdl/msb/Producer.java b/core/src/main/java/io/github/tcdl/msb/Producer.java
index 1fff2443..478129e0 100644
--- a/core/src/main/java/io/github/tcdl/msb/Producer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Producer.java
@@ -7,6 +7,7 @@
 import io.github.tcdl.msb.api.exception.JsonConversionException;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.support.Utils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,10 +36,14 @@ public Producer(ProducerAdapter rawAdapter, String topic, Callback mess
     }
 
     public void publish(Message message) {
+        publish(message, null);
+    }
+
+    public void publish(Message message, String routingKey) {
         try {
             String jsonMessage = Utils.toJson(message, messageMapper);
             LOG.debug("Publishing message to adapter : {}", jsonMessage);
-            rawAdapter.publish(jsonMessage);
+            rawAdapter.publish(jsonMessage, routingKey != null ? routingKey : StringUtils.EMPTY);
             messageHandler.call(message);
         } catch (ChannelException | JsonConversionException e) {
             LOG.error("Exception while message publish to adapter", e);
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
index 2100a413..8219ba7f 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
@@ -1,9 +1,12 @@
 package io.github.tcdl.msb.adapters;
 
+import io.github.tcdl.msb.api.MessageDestination;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
 
+import java.util.Set;
+
 /**
  * MSBAdapterFactory interface represents a common way for creation a particular AdapterFactory
  * accordingly to MSB Configuration and associated with a proper Topic.
@@ -25,6 +28,13 @@ public interface AdapterFactory {
      */
     ProducerAdapter createProducerAdapter(String topic);
 
+    /**
+     * @param destination message destination
+     * @return Producer Adapter associated with a topic
+     * @throws ChannelException if some problems during creation were occurred
+     */
+    ProducerAdapter createProducerAdapter(MessageDestination destination);
+
     /**
      * @param topic topic name
      * @param isResponseTopic specify if this topic used to handle response
@@ -33,6 +43,16 @@ public interface AdapterFactory {
      */
     ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic);
 
+    /**
+     * Creates ConsumerAdapter associated with a topic. Implementation must guarantee
+     * that bindings by provided routing keys are created. However, it does not guarantee
+     * that other bindings for the same queue do or do not exist.
+     * @param topic topic name
+     * @param routingKeys routing keys to be used for binding
+     * @throws ChannelException if a problems has occurred during creation
+     */
+    ConsumerAdapter createConsumerAdapter(String topic, Set routingKeys);
+
     /**
      * @return true if custom MSB threading model should be used.
      * @return false if {@link io.github.tcdl.msb.MessageHandler} should be invoked directly.
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/ProducerAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/ProducerAdapter.java
index 9ab4981f..d97c290a 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/ProducerAdapter.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/ProducerAdapter.java
@@ -16,4 +16,12 @@ public interface ProducerAdapter {
      * @throws ChannelException if some problems during publishing message to Broker were occurred
      */
     void publish(String jsonMessage);
+
+    /**
+     * Publishes the message to the associated topic with specified routing key
+     *
+     * @param jsonMessage message to publish in JSON format
+     * @param routingKey non null String of max length 255 bytes to be used for message routing
+     */
+    void publish(String jsonMessage, String routingKey);
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java b/core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java
new file mode 100644
index 00000000..606c383e
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java
@@ -0,0 +1,51 @@
+package io.github.tcdl.msb.api;
+
+import org.apache.commons.lang3.Validate;
+
+public class MessageDestination {
+
+    private final String topic;
+    private final String routingKey;
+
+    public MessageDestination(String topic, String routingKey) {
+        Validate.notNull(topic, "topic is mandatory");
+        Validate.notNull(routingKey, "routingKey is mandatory");
+        this.topic = topic;
+        this.routingKey = routingKey;
+    }
+
+    public String getTopic() {
+        return topic;
+    }
+
+    public String getRoutingKey() {
+        return routingKey;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        MessageDestination that = (MessageDestination) o;
+
+        if (!topic.equals(that.topic)) return false;
+        return routingKey.equals(that.routingKey);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = topic.hashCode();
+        result = 31 * result + routingKey.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "MessageDestination{" +
+                "topic='" + topic + '\'' +
+                ", routingKey='" + routingKey + '\'' +
+                '}';
+    }
+}
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
index 0b1b0fef..d5bcbb02 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
@@ -6,6 +6,10 @@
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
 
 import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Set;
+
+import static javafx.scene.input.KeyCode.T;
 
 /**
  * Provides methods for creation client-facing API classes.
@@ -86,6 +90,56 @@ public Type getType() {
         });
     }
 
+    /**
+     * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)}
+     */
+    default ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
+                                              ResponderServer.RequestHandler requestHandler){
+        return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, JsonNode.class);
+    }
+
+    /**
+     * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)}
+     */
+    default  ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
+                                              ResponderServer.RequestHandler requestHandler, Class payloadClass) {
+        return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, null, payloadClass);
+    }
+
+    /**
+     * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)}
+     */
+    default  ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
+                                              ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) {
+        return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, errorHandler, new TypeReference() {
+            @Override
+            public Type getType() {
+                return payloadClass;
+            }
+        });
+    }
+
+    /**
+     * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)}
+     */
+    default  ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
+                                                      ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) {
+        return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, null, payloadTypeReference);
+    }
+
+    /**
+     * @param namespace                 topic on a bus for listening on incoming requests
+     * @param routingKeys               routing keys
+     * @param messageTemplate           template of response message
+     * @param requestHandler            handler for processing the request
+     * @param errorHandler              handler for errors to be called after default
+     * @param payloadTypeReference      expected payload type of incoming messages
+     * @return new instance of a {@link ResponderServer} that unmarshals payload into specified payload type
+     */
+     ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
+                                              ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference);
+
+
     default  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) {
         return createResponderServer(namespace, messageTemplate, requestHandler, errorHandler, new TypeReference() {
@@ -112,6 +166,15 @@ public Type getType() {
      */
      Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate);
 
+    /**
+     * Creates requester that doesn't wait for any responses or acknowledgments
+     *
+     * @param messageDestination {@link MessageDestination} to be used
+     * @param messageTemplate {@link MessageTemplate} to be used
+     * @return new instance of a {@link Requester} with original message
+     */
+     Requester createRequesterForFireAndForget(MessageDestination messageDestination, MessageTemplate messageTemplate);
+
     /**
      * @param namespace                 topic on a bus for listening on incoming requests
      * @param messageTemplate           template used for creating response messages
diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
index 332e0d3f..f72944bc 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
@@ -1,5 +1,12 @@
 package io.github.tcdl.msb.api;
 
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Specifies waiting policy (for acknowledgements and responses) for requests sent using {@link Requester}.
  */
@@ -34,12 +41,15 @@ public class RequestOptions {
 
     private final MessageTemplate messageTemplate;
 
-    private RequestOptions(Integer ackTimeout, Integer responseTimeout, Integer waitForResponses, MessageTemplate messageTemplate, String forwardNamespace) {
+    private final String routingKey;
+
+    private RequestOptions(Integer ackTimeout, Integer responseTimeout, Integer waitForResponses, MessageTemplate messageTemplate, String forwardNamespace, String routingKey) {
         this.ackTimeout = ackTimeout;
         this.responseTimeout = responseTimeout;
         this.waitForResponses = waitForResponses;
         this.messageTemplate = messageTemplate;
         this.forwardNamespace = forwardNamespace;
+        this.routingKey = routingKey;
     }
 
     public Integer getAckTimeout() {
@@ -50,7 +60,7 @@ public Integer getResponseTimeout() {
         return responseTimeout;
     }
 
-   public int getWaitForResponses() {
+    public int getWaitForResponses() {
         if (waitForResponses == null || waitForResponses == -1) {
             // use for Infinity number or expected responses
             return WAIT_FOR_RESPONSES_UNTIL_TIMEOUT;
@@ -67,6 +77,14 @@ public String getForwardNamespace() {
         return forwardNamespace;
     }
 
+    public String getRoutingKey() {
+        return routingKey;
+    }
+
+    public boolean hasRoutingKey(){
+        return StringUtils.isNotBlank(routingKey);
+    }
+
     @Override
     public String toString() {
         return "RequestOptions [ackTimeout=" + ackTimeout
@@ -79,12 +97,18 @@ public String toString() {
 
     public static class Builder {
 
+        private String routingKey;
         private Integer ackTimeout;
         private Integer responseTimeout;
         private Integer waitForResponses;
         private MessageTemplate messageTemplate;
         private String forwardNamespace;
 
+        public Builder withRoutingKey(String routingKey) {
+            this.routingKey = routingKey;
+            return this;
+        }
+
         public Builder withAckTimeout(Integer ackTimeout) {
             this.ackTimeout = ackTimeout;
             return this;
@@ -120,11 +144,13 @@ public Builder from(RequestOptions source) {
             this.waitForResponses = source.waitForResponses;
             this.messageTemplate = source.messageTemplate;
             this.forwardNamespace = source.forwardNamespace;
+            this.routingKey = source.routingKey;
             return this;
         }
 
         public RequestOptions build() {
-            return new RequestOptions(ackTimeout, responseTimeout, waitForResponses, messageTemplate, forwardNamespace);
+            return new RequestOptions(ackTimeout, responseTimeout, waitForResponses, messageTemplate, forwardNamespace,
+                    routingKey != null ? routingKey : StringUtils.EMPTY);
         }
     }
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
index 479292cc..b95c9430 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
@@ -1,22 +1,20 @@
 package io.github.tcdl.msb.impl;
 
 import com.fasterxml.jackson.core.type.TypeReference;
-import io.github.tcdl.msb.api.Callback;
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.ObjectFactory;
-import io.github.tcdl.msb.api.PayloadConverter;
-import io.github.tcdl.msb.api.RequestOptions;
-import io.github.tcdl.msb.api.Requester;
-import io.github.tcdl.msb.api.ResponderServer;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.monitor.AggregatorStats;
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.monitor.aggregator.DefaultChannelMonitorAggregator;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.Set;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.ThreadFactory;
@@ -70,6 +68,15 @@ public  Requester createRequesterForSingleResponse(String namespace, Class
         return RequesterImpl.create(namespace, requestOptions, msbContext, toTypeReference(payloadClass));
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public  ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
+                                                     ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) {
+        return ResponderServerImpl.create(namespace, routingKeys, messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -78,13 +85,26 @@ public  Requester createRequesterForFireAndForget(String namespace) {
         return createRequesterForFireAndForget(namespace, null);
     }
 
+    @Override
     public  Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate){
-        RequestOptions requestOptions = new RequestOptions.Builder()
+        RequestOptions.Builder optionsBuilder = new RequestOptions.Builder()
                 .withMessageTemplate(messageTemplate)
-                .withWaitForResponses(0)
-                .build();
+                .withWaitForResponses(0);
+
+        return RequesterImpl.create(namespace, optionsBuilder.build(), msbContext, null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public  Requester createRequesterForFireAndForget(MessageDestination destination, MessageTemplate messageTemplate) {
+        RequestOptions.Builder optionsBuilder = new RequestOptions.Builder()
+                .withMessageTemplate(messageTemplate)
+                .withRoutingKey(destination.getRoutingKey())
+                .withWaitForResponses(0);
 
-        return RequesterImpl.create(namespace, requestOptions, msbContext, null);
+        return RequesterImpl.create(destination.getTopic(), optionsBuilder.build(), msbContext, null);
     }
 
     /**
@@ -93,7 +113,7 @@ public  Requester createRequesterForFireAndForget(String namespace, Messag
     @Override
     public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) {
-        return ResponderServerImpl.create(namespace, messageTemplate, msbContext, requestHandler, null, payloadTypeReference);
+        return createResponderServer(namespace, messageTemplate, requestHandler, null, payloadTypeReference);
     }
 
     /**
@@ -102,7 +122,7 @@ public  ResponderServer createResponderServer(String namespace, MessageTempla
     @Override
     public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) {
-        return ResponderServerImpl.create(namespace, messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference);
+        return ResponderServerImpl.create(namespace, Collections.emptySet(), messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference);
     }
 
     /**
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
index 618b9f2f..adc4700a 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
@@ -2,6 +2,7 @@
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import io.github.tcdl.msb.ChannelManager;
+import io.github.tcdl.msb.Producer;
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.Message;
@@ -143,6 +144,8 @@ public void publish(Object requestPayload, Message originalMessage, String... ta
 
     private void publish(boolean invokeHandlersDirectly, RequestOptions requestOptions, Object requestPayload, Message originalMessage, String... tags) {
         MessageTemplate messageTemplate = MessageTemplate.copyOf(requestOptions.getMessageTemplate());
+
+
         if (tags != null) {
             for(String tag: tags) {
                 if(tag != null) {
@@ -157,21 +160,24 @@ private void publish(boolean invokeHandlersDirectly, RequestOptions requestOptio
                 originalMessage);
 
         Message message = messageFactory.createRequestMessage(messageBuilder, requestPayload);
+        String topic = message.getTopics().getTo();
 
         //use Collector instance to handle expected responses/acks
         if (isWaitForAckMs() || isWaitForResponses()) {
-            String topic = message.getTopics().getResponse();
 
-            Collector collector = createCollector(topic, message, requestOptions, context, eventHandlers, invokeHandlersDirectly);
+            Collector collector = createCollector(message.getTopics().getResponse(), message, requestOptions, context, eventHandlers, invokeHandlersDirectly);
             collector.listenForResponses();
 
-            getChannelManager().findOrCreateProducer(message.getTopics().getTo())
-                    .publish(message);
+            getChannelManager().findOrCreateProducer(topic).publish(message);
 
             collector.waitForResponses();
         } else {
-            getChannelManager().findOrCreateProducer(message.getTopics().getTo())
-                    .publish(message);
+            if(requestOptions.hasRoutingKey()){
+                MessageDestination destination = new MessageDestination(topic, requestOptions.getRoutingKey());
+                getChannelManager().findOrCreateProducer(destination).publish(message, requestOptions.getRoutingKey());
+            } else {
+                getChannelManager().findOrCreateProducer(topic).publish(message);
+            }
         }
     }
 
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
index df85d12a..95965838 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
@@ -3,6 +3,7 @@
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.github.tcdl.msb.ChannelManager;
+import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.support.Utils;
@@ -11,11 +12,13 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Optional;
+import java.util.Set;
 
 public class ResponderServerImpl implements ResponderServer {
     private static final Logger LOG = LoggerFactory.getLogger(ResponderServerImpl.class);
 
     private String namespace;
+    private Set routingKeys;
     private MsbContextImpl msbContext;
     private MessageTemplate messageTemplate;
     private RequestHandler requestHandler;
@@ -24,12 +27,14 @@ public class ResponderServerImpl implements ResponderServer {
     private TypeReference payloadTypeReference;
 
     private ResponderServerImpl(String namespace,
-            MessageTemplate messageTemplate,
-            MsbContextImpl msbContext,
-            RequestHandler requestHandler,
-            ErrorHandler errorHandler,
-            TypeReference payloadTypeReference) {
+                                Set routingKeys,
+                                MessageTemplate messageTemplate,
+                                MsbContextImpl msbContext,
+                                RequestHandler requestHandler,
+                                ErrorHandler errorHandler,
+                                TypeReference payloadTypeReference) {
         this.namespace = namespace;
+        this.routingKeys = routingKeys;
         this.messageTemplate = messageTemplate;
         this.msbContext = msbContext;
         this.requestHandler = requestHandler;
@@ -42,9 +47,9 @@ private ResponderServerImpl(String namespace,
     /**
      * {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(String, MessageTemplate, RequestHandler, ErrorHandler, Class)}
      */
-    static  ResponderServerImpl create(String namespace,  MessageTemplate messageTemplate, MsbContextImpl msbContext,
+    static  ResponderServerImpl create(String namespace, Set routingKeys, MessageTemplate messageTemplate, MsbContextImpl msbContext,
             RequestHandler requestHandler,  ErrorHandler errorHandler, TypeReference payloadTypeReference) {
-        return new ResponderServerImpl<>(namespace, messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference);
+        return new ResponderServerImpl<>(namespace, routingKeys, messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference);
     }
 
     /**
@@ -59,13 +64,18 @@ static  ResponderServerImpl create(String namespace,  MessageTemplate mess
     public ResponderServer listen() {
         ChannelManager channelManager = msbContext.getChannelManager();
 
-        channelManager.subscribe(namespace,
-                (incomingMessage, acknowledgeHandler) -> {
-                    LOG.debug("[{}] Received message with id: [{}]", namespace, incomingMessage.getId());
-                    Responder responder = createResponder(incomingMessage);
-                    ResponderContext responderContext = createResponderContext(responder, acknowledgeHandler, incomingMessage);
-                    onResponder(responderContext);
-                });
+        MessageHandler messageHandler = (incomingMessage, acknowledgeHandler) -> {
+            LOG.debug("[{}] Received message with id: [{}]", namespace, incomingMessage.getId());
+            Responder responder = createResponder(incomingMessage);
+            ResponderContext responderContext = createResponderContext(responder, acknowledgeHandler, incomingMessage);
+            onResponder(responderContext);
+        };
+
+        if (routingKeys == null || routingKeys.isEmpty()) {
+            channelManager.subscribe(namespace, messageHandler);
+        } else {
+            channelManager.subscribe(namespace, routingKeys, messageHandler);
+        }
 
         return this;
     }
diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java
index ec5aa233..a9e7189d 100644
--- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java
@@ -1,16 +1,13 @@
 package io.github.tcdl.msb;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
 
 import io.github.tcdl.msb.adapters.AdapterFactory;
 import io.github.tcdl.msb.adapters.AdapterFactoryLoader;
+import io.github.tcdl.msb.api.MessageDestination;
 import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException;
+import io.github.tcdl.msb.api.exception.MsbException;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent;
@@ -18,6 +15,7 @@
 import io.github.tcdl.msb.support.TestUtils;
 
 import java.time.Clock;
+import java.util.Collections;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -27,15 +25,20 @@
 import io.github.tcdl.msb.threading.MessageHandlerInvoker;
 import io.github.tcdl.msb.threading.ThreadPoolMessageHandlerInvoker;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.rules.ExpectedException;
 
 public class ChannelManagerTest {
 
     private ChannelManager channelManager;
     private ChannelMonitorAgent mockChannelMonitorAgent;
 
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
     @Before
     public void setUp() {
         MsbConfig msbConfig = TestUtils.createMsbConfigurations();
@@ -77,6 +80,56 @@ public void testConsumerSubscribeMultipleSameTopic() {
         channelManager.subscribe(topic, (message, acknowledgeHandler) -> {});
     }
 
+    @Test
+    public void testSubscribeSameTopicDifferentRoutingKeys() throws Exception {
+        String topic = "interesting:topic";
+        String routingKey1 = "routing.key.one";
+        String routingKey2 = "routing.key.two";
+
+        channelManager.subscribe(topic, Collections.singleton(routingKey1), (message, acknowledgeHandler) -> {});
+        verify(mockChannelMonitorAgent).consumerTopicCreated(topic);
+        expectedException.expect(ConsumerSubscriptionException.class);
+        channelManager.subscribe(topic, Collections.singleton(routingKey2), (message, acknowledgeHandler) -> {});
+    }
+
+    @Test
+    public void testFindOrCreateProducerForDestination() throws Exception {
+
+        String topic = "interesting:topic";
+        String routingKey1 = "routing.key.one";
+        String routingKey2 = "routing.key.two";
+
+        MessageDestination destination1 = new MessageDestination(topic, routingKey1);
+        MessageDestination destination2 = new MessageDestination(topic, routingKey2);
+
+        Producer producer1 = channelManager.findOrCreateProducer(destination1);
+        Producer producer2 = channelManager.findOrCreateProducer(destination2);
+
+        assertEquals("Multicast producer must be the same for same topic", producer1, producer2);
+    }
+
+    @Test
+    public void testFindOrCreateProducer_broadcastProducerAlreadyExists() throws Exception {
+        String topic = "interesting:topic";
+        String routingKey = "routing.key";
+
+        MessageDestination destination = new MessageDestination(topic, routingKey);
+        channelManager.findOrCreateProducer(topic);
+        expectedException.expect(MsbException.class);
+        channelManager.findOrCreateProducer(destination);
+    }
+
+    @Test
+    public void testFindOrCreateProducer_multicastProducerAlreadyExists() throws Exception {
+        String topic = "interesting:topic";
+        String routingKey = "routing.key";
+
+        MessageDestination destination = new MessageDestination(topic, routingKey);
+        channelManager.findOrCreateProducer(destination);
+        expectedException.expect(MsbException.class);
+        channelManager.findOrCreateProducer(topic);
+    }
+
     @Test
     public void testPublishMessageInvokesAgent() {
         String topic = "topic:test-agent-publish";
@@ -110,7 +163,7 @@ public void testReceiveMessageInvokesAgentAndEmitsEvent() throws InterruptedExce
     }
 
     @Test
-    public void testSubscribeUnsubscribe() {
+    public void testSubscribeUnsubscribeFromBroadcast() {
         String topic = "topic:test-unsubscribe-once";
 
         channelManager.subscribe(topic, (message, acknowledgeHandler) -> {});
@@ -119,6 +172,16 @@ public void testSubscribeUnsubscribe() {
         verify(mockChannelMonitorAgent).consumerTopicRemoved(topic);
     }
 
+    @Test
+    public void testSubscribeUnsubscribeFromMulticast() {
+        String topic = "topic:test-unsubscribe-once";
+
+        channelManager.subscribe(topic, Collections.singleton("routing.key"), (message, acknowledgeHandler) -> {});
+        channelManager.unsubscribe(topic);
+
+        verify(mockChannelMonitorAgent).consumerTopicRemoved(topic);
+    }
+
     @Test
     public void testSubscribeUnsubscribeSeparateTopics() {
         String topic1 = "topic:test-unsubscribe-try-first";
diff --git a/core/src/test/java/io/github/tcdl/msb/ProducerTest.java b/core/src/test/java/io/github/tcdl/msb/ProducerTest.java
index f1190017..5c0d3705 100644
--- a/core/src/test/java/io/github/tcdl/msb/ProducerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ProducerTest.java
@@ -14,6 +14,7 @@
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.support.TestUtils;
 import io.github.tcdl.msb.support.Utils;
+import org.apache.commons.lang3.StringUtils;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -27,6 +28,7 @@
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
@@ -77,12 +79,20 @@ public void testPublishVerifyHandlerCalled() {
         verify(handlerMock).call(any(Message.class));
     }
 
+    @Test
+    public void testPublishWithDefaultRoutingKey() throws Exception {
+        Message originaMessage = TestUtils.createSimpleRequestMessage(TOPIC);
+        Producer producer = new Producer(adapterMock, TOPIC, handlerMock, messageMapper);
+        producer.publish(originaMessage, null);
+        verify(adapterMock).publish(anyString(), eq(StringUtils.EMPTY));
+    }
+
     @Test(expected = ChannelException.class)
     @SuppressWarnings("unchecked")
     public void testPublishRawAdapterThrowChannelException() throws ChannelException {
         Message originaMessage = TestUtils.createSimpleRequestMessage(TOPIC);
 
-        Mockito.doThrow(ChannelException.class).when(adapterMock).publish(anyString());
+        Mockito.doThrow(ChannelException.class).when(adapterMock).publish(anyString(), anyString());
 
         Producer producer = new Producer(adapterMock, TOPIC, handlerMock, messageMapper);
         producer.publish(originaMessage);
diff --git a/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java b/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java
index 5ae8bae4..c19bcbfc 100644
--- a/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java
+++ b/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java
@@ -23,6 +23,7 @@
 import java.util.concurrent.TimeUnit;
 
 import io.github.tcdl.msb.mock.adapterfactory.TestMsbStorageForAdapterFactory;
+import org.apache.commons.lang3.StringUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -67,7 +68,7 @@ public void testAnnouncementUnexpectedMessage() throws InterruptedException {
         CountDownLatch announcementReceived = monitorPrepareAwaitOnAnnouncement(TOPIC_NAME);
 
         //simulate broken announcement in broker
-        storage.publishIncomingMessage(Utils.TOPIC_ANNOUNCE,
+        storage.publishIncomingMessage(Utils.TOPIC_ANNOUNCE, StringUtils.EMPTY,
                 Utils.toJson(TestUtils.createSimpleRequestMessage(Utils.TOPIC_ANNOUNCE), msbContext.getPayloadMapper()));
 
         assertFalse("Broken announcement was handled", announcementReceived.await(RequesterResponderIT.MESSAGE_TRANSMISSION_TIME / 2, TimeUnit.MILLISECONDS));
@@ -120,7 +121,7 @@ public void testHeartbeatMessage() throws InterruptedException {
         Message responseMessage = TestUtils.createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(),
                 requestMessage.getCorrelationId(),
                 payload);
-        storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), Utils.toJson(responseMessage, msbContext.getPayloadMapper()));
+        storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), StringUtils.EMPTY, Utils.toJson(responseMessage, msbContext.getPayloadMapper()));
 
         assertTrue("Heartbeat response was not received",
                 heartBeatResponseReceived.await(HEARTBEAT_TIMEOUT_MS * 2, TimeUnit.MILLISECONDS));
@@ -159,9 +160,9 @@ public void testHeartbeatUnexpectedMessage() throws InterruptedException {
                 .createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(), requestMessage.getCorrelationId(),
                         payload);
         //simulate 3 heartbeatResponses: 1 valid and 2 broken
-        storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), Utils.toJson(brokenResponseMessage1, msbContext.getPayloadMapper()));
-        storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), Utils.toJson(responseMessage, msbContext.getPayloadMapper()));
-        storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), Utils.toJson(brokenResponseMessage2, msbContext.getPayloadMapper()));
+        storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), StringUtils.EMPTY, Utils.toJson(brokenResponseMessage1, msbContext.getPayloadMapper()));
+        storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), StringUtils.EMPTY, Utils.toJson(responseMessage, msbContext.getPayloadMapper()));
+        storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), StringUtils.EMPTY, Utils.toJson(brokenResponseMessage2, msbContext.getPayloadMapper()));
 
         assertTrue("Heartbeat response was not received",
                 heartBeatResponseReceived.await(HEARTBEAT_TIMEOUT_MS * 2, TimeUnit.MILLISECONDS));
diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
index bc8fd35d..4cf76511 100644
--- a/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/api/RequestOptionsTest.java
@@ -54,6 +54,7 @@ public void testBuilderFromExistingRequestOptions() throws Exception {
         Integer responseTimeout = 2;
         String forwardNamespace = "forward:namespace";
         int waitForResponses = 3;
+        String routingKey = "routing.key";
 
         RequestOptions source = new RequestOptions.Builder()
                 .withAckTimeout(ackTimeout)
@@ -61,6 +62,7 @@ public void testBuilderFromExistingRequestOptions() throws Exception {
                 .withForwardNamespace(forwardNamespace)
                 .withWaitForResponses(waitForResponses)
                 .withMessageTemplate(sourceMessageTemplate)
+                .withRoutingKey(routingKey)
                 .build();
 
         RequestOptions.Builder builder = new RequestOptions.Builder().from(source);
@@ -71,5 +73,6 @@ public void testBuilderFromExistingRequestOptions() throws Exception {
         assertEquals(waitForResponses, result.getWaitForResponses());
         assertEquals(forwardNamespace, result.getForwardNamespace());
         assertSame(sourceMessageTemplate, result.getMessageTemplate());
+        assertEquals(routingKey, result.getRoutingKey());
     }
 }
\ No newline at end of file
diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java
index 6aa7fea7..dc8857a8 100644
--- a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java
+++ b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java
@@ -20,6 +20,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 import io.github.tcdl.msb.mock.adapterfactory.TestMsbStorageForAdapterFactory;
+import org.apache.commons.lang3.StringUtils;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -198,7 +199,7 @@ public void testResponderCommunicationWithAck() throws Exception {
                 })
                 .listen();
 
-        storage.publishIncomingMessage(namespace1,
+        storage.publishIncomingMessage(namespace1, StringUtils.EMPTY,
                 Utils.toJson(TestUtils.createSimpleRequestMessage(namespace1), msbContext.getPayloadMapper()));
 
         assertTrue("Message ack was not send", ackSent.await(MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS));
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
index 13c89bbc..86c3c7d6 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
@@ -14,6 +14,7 @@
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.collector.Collector;
 import io.github.tcdl.msb.support.TestUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
index 7c98cdac..78f7d45d 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
@@ -1,6 +1,7 @@
 package io.github.tcdl.msb.impl;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.collect.Sets;
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.Producer;
@@ -17,7 +18,9 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -67,7 +70,7 @@ public void testResponderServerProcessPayloadSuccess() throws Exception {
         when(spyMsbContext.getChannelManager()).thenReturn(spyChannelManager);
 
         ResponderServerImpl, Object, Map>> responderServer = ResponderServerImpl
-                .create(TOPIC, requestOptions.getMessageTemplate(), spyMsbContext, handler, null,
+                .create(TOPIC, Collections.emptySet(), requestOptions.getMessageTemplate(), spyMsbContext, handler, null,
                         new TypeReference, Object, Map>>() {});
 
         ResponderServerImpl spyResponderServer = (ResponderServerImpl) spy(responderServer).listen();
@@ -96,7 +99,7 @@ public void testResponderServerProcessUnexpectedPayload() throws Exception {
         Message incomingMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText);
 
         ResponderServerImpl responderServer = ResponderServerImpl
-                .create(TOPIC, messageTemplate, msbContext, handler, null, new TypeReference() {});
+                .create(TOPIC, null, messageTemplate, msbContext, handler, null, new TypeReference() {});
         responderServer.listen();
 
         // simulate incoming request
@@ -117,7 +120,7 @@ public void testResponderServerProcessHandlerThrowException() throws Exception {
         ResponderServer.RequestHandler handler = (request, responderContext) -> { throw error; };
 
         ResponderServerImpl responderServer = ResponderServerImpl
-                .create(TOPIC, messageTemplate, msbContext, handler, null, new TypeReference() {});
+                .create(TOPIC, null, messageTemplate, msbContext, handler, null, new TypeReference() {});
         responderServer.listen();
 
         // simulate incoming request
@@ -142,7 +145,7 @@ public void testResponderServerProcessCustomHandlerThrowException() throws Excep
 
         ResponderServer.ErrorHandler errorHandlerMock = mock(ResponderServer.ErrorHandler.class);
         ResponderServerImpl responderServer = ResponderServerImpl
-                .create(TOPIC, messageTemplate, msbContext, handler, errorHandlerMock, new TypeReference() {});
+                .create(TOPIC, null, messageTemplate, msbContext, handler, errorHandlerMock, new TypeReference() {});
         responderServer.listen();
 
         // simulate incoming request
@@ -169,7 +172,7 @@ public void testCreateResponderWithResponseTopic() {
                 .build();
 
         ResponderServerImpl responderServer = ResponderServerImpl
-                .create(TOPIC, messageTemplate, msbContext1, handler, null, new TypeReference() {});
+                .create(TOPIC, null, messageTemplate, msbContext1, handler, null, new TypeReference() {});
 
         Message incomingMessage = TestUtils.createMsbRequestMessageNoPayload(TOPIC);
         Responder responder = responderServer.createResponder(incomingMessage);
@@ -183,6 +186,40 @@ public void testCreateResponderWithResponseTopic() {
         verify(mockProducer, times(2)).publish(any(Message.class));
     }
 
+    @Test
+    public void testCreateResponderWithRoutingKeys() throws Exception {
+
+        ChannelManager mockChannelManager = mock(ChannelManager.class);
+        MsbContextImpl msbContext = new TestUtils.TestMsbContextBuilder()
+                .withChannelManager(mockChannelManager)
+                .build();
+
+        Set routingKeys = Sets.newHashSet("routing.key.one", "routing.key.two");
+
+        ResponderServer.RequestHandler requestHandler = (request, responderContext) -> {};
+        ResponderServerImpl responderServer = ResponderServerImpl
+                .create(TOPIC, routingKeys, messageTemplate, msbContext, requestHandler, null, new TypeReference() {});
+
+        responderServer.listen();
+        verify(mockChannelManager).subscribe(eq(TOPIC), eq(routingKeys), any(MessageHandler.class));
+    }
+
+    @Test
+    public void testCreateResponderWithoutRoutingKeys() throws Exception {
+
+        ChannelManager mockChannelManager = mock(ChannelManager.class);
+        MsbContextImpl msbContext = new TestUtils.TestMsbContextBuilder()
+                .withChannelManager(mockChannelManager)
+                .build();
+
+        ResponderServer.RequestHandler requestHandler = (request, responderContext) -> {};
+        ResponderServerImpl responderServer = ResponderServerImpl
+                .create(TOPIC, Collections.emptySet(), messageTemplate, msbContext, requestHandler, null, new TypeReference() {});
+
+        responderServer.listen();
+        verify(mockChannelManager).subscribe(eq(TOPIC), any(MessageHandler.class));
+    }
+
     @Test
     public void testCreateResponderNoResponseTopic() {
         ResponderServer.RequestHandler handler = (request, responderContext) -> {
@@ -194,7 +231,7 @@ public void testCreateResponderNoResponseTopic() {
                 .build();
 
         ResponderServerImpl responderServer = ResponderServerImpl
-                .create(TOPIC, messageTemplate, msbContext, handler, null, new TypeReference() {});
+                .create(TOPIC, null, messageTemplate, msbContext, handler, null, new TypeReference() {});
 
         Message incomingMessage = TestUtils.createMsbBroadcastMessageNoPayload(TOPIC);
         Responder responder = responderServer.createResponder(incomingMessage);
@@ -216,7 +253,7 @@ public void testStop() throws Exception {
                 .build();
 
         ResponderServerImpl responderServer = ResponderServerImpl.create(
-                TOPIC, messageTemplate, msbContext, doNothingHandler, null, new TypeReference() {}
+                TOPIC, null, messageTemplate, msbContext, doNothingHandler, null, new TypeReference() {}
         );
 
         responderServer.stop();
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java
index f829a044..30b92e12 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java
@@ -3,7 +3,11 @@
 import io.github.tcdl.msb.adapters.AdapterFactory;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
+import io.github.tcdl.msb.api.MessageDestination;
 import io.github.tcdl.msb.config.MsbConfig;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Set;
 
 /**
  * This AdapterFactory implementation is used to capture/submit raw messages as JSON and could be used during testing.
@@ -32,6 +36,14 @@ public ProducerAdapter createProducerAdapter(String namespace) {
         return producerAdapter;
     }
 
+    @Override
+    public ProducerAdapter createProducerAdapter(MessageDestination destination) {
+        String namespace = destination.getTopic();
+        TestMsbProducerAdapter producerAdapter = new TestMsbProducerAdapter(namespace, storage);
+        storage.addProducerAdapter(namespace, producerAdapter);
+        return producerAdapter;
+    }
+
     @Override
     public ConsumerAdapter createConsumerAdapter(String namespace, boolean isResponseTopic) {
         TestMsbConsumerAdapter consumerAdapter = new TestMsbConsumerAdapter(namespace, storage);
@@ -39,6 +51,13 @@ public ConsumerAdapter createConsumerAdapter(String namespace, boolean isRespons
         return consumerAdapter;
     }
 
+    @Override
+    public ConsumerAdapter createConsumerAdapter(String namespace, Set routingKeys) {
+        TestMsbConsumerAdapter consumerAdapter = new TestMsbConsumerAdapter(namespace, storage);
+        storage.addConsumerAdapter(namespace, routingKeys, consumerAdapter);
+        return consumerAdapter;
+    }
+
     @Override
     public boolean isUseMsbThreadingModel() {
         return false;
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbProducerAdapter.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbProducerAdapter.java
index 7ec8a0e9..fa39e1a8 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbProducerAdapter.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbProducerAdapter.java
@@ -1,6 +1,9 @@
 package io.github.tcdl.msb.mock.adapterfactory;
 
 import io.github.tcdl.msb.adapters.ProducerAdapter;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Set;
 
 public class TestMsbProducerAdapter implements ProducerAdapter {
 
@@ -15,7 +18,13 @@ public TestMsbProducerAdapter(String namespace, TestMsbStorageForAdapterFactory
 
     @Override
     public void publish(String jsonMessage) {
-        storage.addPublishedTestMessage(namespace, jsonMessage);
-        storage.publishIncomingMessage(namespace, jsonMessage);
+        storage.addPublishedTestMessage(namespace, StringUtils.EMPTY, jsonMessage);
+        storage.publishIncomingMessage(namespace, StringUtils.EMPTY, jsonMessage);
+    }
+
+    @Override
+    public void publish(String jsonMessage, String routingKey) {
+        storage.addPublishedTestMessage(namespace, routingKey, jsonMessage);
+        storage.publishIncomingMessage(namespace, routingKey, jsonMessage);
     }
 }
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java
index 63eb41a9..462f9ffa 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java
@@ -2,24 +2,27 @@
 
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.api.MsbContext;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
 import org.apache.commons.lang3.reflect.FieldUtils;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * This class provides storage and accessors for {@link TestMsbAdapterFactory}-based
  * testing.
  */
 public class TestMsbStorageForAdapterFactory {
-    private final HashMap consumers = new HashMap<>();
+
+    private final HashMap, TestMsbConsumerAdapter>> multicastConsumers = new HashMap<>();
+    private final HashMap broadcastConsumers = new HashMap<>();
     private final HashMap producers = new HashMap<>();
-    private final HashMap> publishedMessages = new HashMap<>();
+    private final HashMap>> publishedMessages = new HashMap<>();
 
     /**
      * Get TestMsbStorageForAdapterFactory instance used by a MsbContext.
+     *
      * @param msbContext
      * @return
      */
@@ -36,6 +39,7 @@ public static TestMsbStorageForAdapterFactory extract(MsbContext msbContext) {
     /**
      * Force other context to use this TestMsbStorageForAdapterFactory instance so messaging will be shared
      * between different contexts. Without this action, an MsbContext handles its own messages only.
+     *
      * @param otherContext
      */
     public void connect(MsbContext otherContext) {
@@ -52,65 +56,81 @@ synchronized void addProducerAdapter(String namespace, TestMsbProducerAdapter ad
         producers.put(namespace, adapter);
     }
 
-    synchronized void addConsumerAdapter(String namespace, TestMsbConsumerAdapter adapter) {
-        consumers.put(namespace, adapter);
+    synchronized void addConsumerAdapter(String namespace, Set routingKeys, TestMsbConsumerAdapter adapter) {
+        multicastConsumers.computeIfAbsent(namespace, ns -> new HashMap<>()).replace(routingKeys, adapter);
     }
 
-    synchronized void addPublishedTestMessage(String namespace, String jsonMessage) {
-        List publishedMessages = this.publishedMessages.get(namespace);
-        if (publishedMessages == null) {
-            publishedMessages = new ArrayList<>();
-            this.publishedMessages.put(namespace, publishedMessages);
-        }
-        publishedMessages.add(jsonMessage);
+    synchronized void addConsumerAdapter(String namespace, TestMsbConsumerAdapter adapter) {
+        broadcastConsumers.putIfAbsent(namespace, adapter);
     }
 
+    synchronized void addPublishedTestMessage(String namespace, String routingKey, String jsonMessage) {
+        publishedMessages.computeIfAbsent(namespace, ns -> new HashMap<>())
+                .computeIfAbsent(routingKey, rk -> new ArrayList<>())
+                .add(jsonMessage);
+    }
 
     /**
      * Reset the storage.
      */
     public synchronized void cleanup() {
-        consumers.clear();
+        multicastConsumers.clear();
+        broadcastConsumers.clear();
         producers.clear();
         publishedMessages.clear();
     }
 
     /**
      * Publish a raw JSON message that should be handled as an incoming message.
-     * @param namespace
-     * @param jsonMessage
      */
-    public synchronized void publishIncomingMessage(String namespace, String jsonMessage) {
-        TestMsbConsumerAdapter consumerAdapter = consumers.get(namespace);
-        if(consumerAdapter != null) {
-            consumerAdapter.pushTestMessage(jsonMessage);
+    public synchronized void publishIncomingMessage(String namespace, String routingKey, String jsonMessage) {
+        if (broadcastConsumers.get(namespace) != null) {
+            broadcastConsumers.get(namespace).pushTestMessage(jsonMessage);
+        } else {
+            multicastConsumers.getOrDefault(namespace, Collections.emptyMap()).entrySet().stream()
+                    .filter(entry -> entry.getKey().contains(routingKey))
+                    .forEach(entry -> entry.getValue().pushTestMessage(jsonMessage));
         }
     }
 
     /**
      * Get a list of outgoing raw JSON messages.
-     * @param namespace
-     * @return
      */
     public synchronized List getOutgoingMessages(String namespace) {
-        List publishedMessages = this.publishedMessages.get(namespace);
-        if (publishedMessages == null) {
-            publishedMessages = Collections.emptyList();
-        }
-        return publishedMessages;
+        return publishedMessages.getOrDefault(namespace, Collections.emptyMap())
+                .values()
+                .stream()
+                .flatMap(List::stream)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Get a list of outgoing raw JSON messages.
+     */
+    public synchronized List getOutgoingMessages(String namespace, Set routingKeys) {
+        Validate.notNull(routingKeys, "routingKeys should not be null");
+        return publishedMessages.getOrDefault(namespace, Collections.emptyMap())
+                .entrySet()
+                .stream()
+                .filter(entry -> routingKeys.contains(entry.getKey()))
+                .flatMap(entry -> entry.getValue().stream())
+                .collect(Collectors.toList());
     }
 
     /**
      * Get a single outgoing raw JSON message.
-     * @param namespace
-     * @return
      */
     public synchronized String getOutgoingMessage(String namespace) {
-        List publishedMessages = this.publishedMessages.get(namespace);
-        if (publishedMessages == null || publishedMessages.size() != 1) {
+        return this.getOutgoingMessage(namespace, StringUtils.EMPTY);
+    }
+
+    public synchronized String getOutgoingMessage(String namespace, String routingKey) {
+        List messages = publishedMessages.getOrDefault(namespace, Collections.emptyMap())
+                .getOrDefault(routingKey, Collections.emptyList());
+
+        if (messages == null || messages.size() != 1) {
             throw new RuntimeException("A single outgoing message is expected");
         }
-        return publishedMessages.get(0);
+        return messages.get(0);
     }
-
 }
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java
index 7d0b2685..8662fbb0 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/ResponderCapture.java
@@ -1,8 +1,13 @@
 package io.github.tcdl.msb.mock.objectfactory;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.collect.Sets;
 import io.github.tcdl.msb.api.MessageTemplate;
 import io.github.tcdl.msb.api.ResponderServer;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collections;
+import java.util.Set;
 
 import static org.mockito.Mockito.mock;
 
@@ -12,6 +17,7 @@
  */
 public class ResponderCapture extends AbstractCapture  {
     private final MessageTemplate messageTemplate;
+    private final Set routingKeys;
     private final ResponderServer.RequestHandler requestHandler;
     private final ResponderServer.ErrorHandler errorHandler;
     private final ResponderServer responderServerMock;
@@ -20,7 +26,15 @@ public ResponderCapture(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler,
             ResponderServer.ErrorHandler errorHandler,
             TypeReference payloadTypeReference, Class payloadClass) {
+        this(namespace, Collections.emptySet(), messageTemplate, requestHandler, errorHandler, payloadTypeReference, payloadClass);
+    }
+
+    public ResponderCapture(String namespace, Set routingKeys, MessageTemplate messageTemplate,
+                            ResponderServer.RequestHandler requestHandler,
+                            ResponderServer.ErrorHandler errorHandler,
+                            TypeReference payloadTypeReference, Class payloadClass) {
         super(namespace, payloadTypeReference, payloadClass);
+        this.routingKeys = routingKeys;
         this.messageTemplate = messageTemplate;
         this.requestHandler = requestHandler;
         this.errorHandler = errorHandler;
@@ -42,4 +56,8 @@ public ResponderServer.ErrorHandler getErrorHandler() {
     public ResponderServer getResponderServerMock() {
         return responderServerMock;
     }
+
+    public Set getRoutingKeys() {
+        return Collections.unmodifiableSet(routingKeys);
+    }
 }
\ No newline at end of file
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
index a63df1f2..562958dc 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
@@ -7,6 +7,7 @@
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
 
 import java.lang.reflect.Type;
+import java.util.Set;
 
 /**
  * {@link ObjectFactory} implementation that captures all requesters/responders params and callbacks to be
@@ -94,6 +95,16 @@ public  ResponderServer createResponderServer(String namespace, MessageTempla
         return capture.getResponderServerMock();
     }
 
+    @Override
+    public  ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
+                                                     ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler,
+                                                     TypeReference payloadTypeReference) {
+
+        ResponderCapture capture = new ResponderCapture<>(namespace, routingKeys, messageTemplate, requestHandler, null, payloadTypeReference, null);
+        storage.addCapture(capture);
+        return capture.getResponderServerMock();
+    }
+
     @Override
     public  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
             ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) {
@@ -115,6 +126,15 @@ public  Requester createRequesterForFireAndForget(String namespace, Messag
         return createRequester(namespace, requestOptions, (Class)null);
     }
 
+    @Override
+    public  Requester createRequesterForFireAndForget(MessageDestination destination, MessageTemplate messageTemplate) {
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(0)
+                .withRoutingKey(destination.getRoutingKey())
+                .build();
+        return createRequester(destination.getTopic(), requestOptions, (Class)null);
+    }
+
     @Override
     public PayloadConverter getPayloadConverter() {
         return null;
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java
index fe808df4..ba62309f 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbStorageForObjectFactory.java
@@ -1,7 +1,10 @@
 package io.github.tcdl.msb.mock.objectfactory;
 import io.github.tcdl.msb.api.MsbContext;
+import org.apache.commons.lang3.StringUtils;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
diff --git a/pom.xml b/pom.xml
index 5eb92f35..89d730d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,6 +75,11 @@
                 commons-io
                 2.4
             
+            
+                commons-collections
+                commons-collections
+                3.2.1
+            
             
                 com.github.fge
                 json-schema-validator

From d7b23b7cfc8381f25b854f88b83235f9b4bb4fb9 Mon Sep 17 00:00:00 2001
From: alex 
Date: Mon, 29 Aug 2016 18:08:14 +0300
Subject: [PATCH 146/226] minor fix in TestMsbStorageForAdapterFactory.java

---
 .../mock/adapterfactory/TestMsbStorageForAdapterFactory.java  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java
index 462f9ffa..484c07ad 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbStorageForAdapterFactory.java
@@ -57,11 +57,11 @@ synchronized void addProducerAdapter(String namespace, TestMsbProducerAdapter ad
     }
 
     synchronized void addConsumerAdapter(String namespace, Set routingKeys, TestMsbConsumerAdapter adapter) {
-        multicastConsumers.computeIfAbsent(namespace, ns -> new HashMap<>()).replace(routingKeys, adapter);
+        multicastConsumers.computeIfAbsent(namespace, ns -> new HashMap<>()).put(routingKeys, adapter);
     }
 
     synchronized void addConsumerAdapter(String namespace, TestMsbConsumerAdapter adapter) {
-        broadcastConsumers.putIfAbsent(namespace, adapter);
+        broadcastConsumers.put(namespace, adapter);
     }
 
     synchronized void addPublishedTestMessage(String namespace, String routingKey, String jsonMessage) {

From 771b472132a39dc0360815d785663f7297c0ea6a Mon Sep 17 00:00:00 2001
From: alex 
Date: Tue, 30 Aug 2016 10:25:29 +0300
Subject: [PATCH 147/226] Routing key support: unified request-response
 behavior

---
 .../github/tcdl/msb/impl/RequesterImpl.java   | 18 +++---
 .../tcdl/msb/impl/RequesterImplTest.java      | 62 ++++++++++++++-----
 2 files changed, 59 insertions(+), 21 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
index adc4700a..0990d671 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
@@ -168,16 +168,20 @@ private void publish(boolean invokeHandlersDirectly, RequestOptions requestOptio
             Collector collector = createCollector(message.getTopics().getResponse(), message, requestOptions, context, eventHandlers, invokeHandlersDirectly);
             collector.listenForResponses();
 
-            getChannelManager().findOrCreateProducer(topic).publish(message);
+            publishMessage(topic, requestOptions, message);
 
             collector.waitForResponses();
         } else {
-            if(requestOptions.hasRoutingKey()){
-                MessageDestination destination = new MessageDestination(topic, requestOptions.getRoutingKey());
-                getChannelManager().findOrCreateProducer(destination).publish(message, requestOptions.getRoutingKey());
-            } else {
-                getChannelManager().findOrCreateProducer(topic).publish(message);
-            }
+            publishMessage(topic, requestOptions, message);
+        }
+    }
+
+    private void publishMessage(String topic, RequestOptions requestOptions, Message message) {
+        if (requestOptions.hasRoutingKey()) {
+            MessageDestination destination = new MessageDestination(topic, requestOptions.getRoutingKey());
+            getChannelManager().findOrCreateProducer(destination).publish(message, requestOptions.getRoutingKey());
+        } else {
+            getChannelManager().findOrCreateProducer(topic).publish(message);
         }
     }
 
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
index 86c3c7d6..0edff2bb 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
@@ -4,17 +4,12 @@
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.Consumer;
 import io.github.tcdl.msb.Producer;
-import io.github.tcdl.msb.api.Callback;
-import io.github.tcdl.msb.api.MessageContext;
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.RequestOptions;
-import io.github.tcdl.msb.api.Requester;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.collector.Collector;
 import io.github.tcdl.msb.support.TestUtils;
-import org.apache.commons.lang3.StringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -68,6 +63,16 @@ public void testPublishNoWaitForResponses() throws Exception {
         verify(collectorMock, never()).waitForResponses();
     }
 
+    @Test
+    public void testPublishWithRoutingKeyNoWaitForResponses() throws Exception {
+        RequesterImpl requester = initRequesterForResponsesWith("UK", 0, 0, 0, null, null, null, null);
+
+        publishByAllMethods(requester);
+
+        verify(collectorMock, never()).listenForResponses();
+        verify(collectorMock, never()).waitForResponses();
+    }
+
     @Test
     public void testPublishWaitForResponses() throws Exception {
         RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null);
@@ -78,6 +83,17 @@ public void testPublishWaitForResponses() throws Exception {
         verify(collectorMock, times(4)).waitForResponses();
     }
 
+
+    @Test
+    public void testPublishWithRoutingKeyWaitForResponses() throws Exception {
+        RequesterImpl requester = initRequesterForResponsesWith("UK", 1, 0, 0, null, null, null, null);
+
+        publishByAllMethods(requester);
+
+        verify(collectorMock, times(4)).listenForResponses();
+        verify(collectorMock, times(4)).waitForResponses();
+    }
+
     private void publishByAllMethods(RequesterImpl requester) {
         Message originalMessage = TestUtils.createMsbRequestMessage("some:topic", "body text");
         requester.publish(TestUtils.createSimpleRequestPayload());
@@ -414,10 +430,8 @@ private RequesterImpl initRequesterForResponsesWith(Integer numberO
                                                                      BiConsumer onError,
                                                                      Callback endHandler) throws Exception {
 
-        MessageTemplate messageTemplateMock = mock(MessageTemplate.class);
-
-        RequestOptions requestOptionsMock = new RequestOptions.Builder()
-                .withMessageTemplate(messageTemplateMock)
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withMessageTemplate(mock(MessageTemplate.class))
                 .withWaitForResponses(numberOfResponses)
                 .withResponseTimeout(respTimeout)
                 .withAckTimeout(ackTimeout)
@@ -425,26 +439,46 @@ private RequesterImpl initRequesterForResponsesWith(Integer numberO
 
         when(channelManagerMock.findOrCreateProducer(anyString())).thenReturn(producerMock);
 
+        return setUpRequester(onResponse, onAcknowledge, onError, endHandler, requestOptions);
+    }
+
+    private RequesterImpl initRequesterForResponsesWith(String routingKey, Integer numberOfResponses, Integer respTimeout, Integer ackTimeout,
+                                                                     BiConsumer onResponse, BiConsumer onAcknowledge,
+                                                                     BiConsumer onError,
+                                                                     Callback endHandler) throws Exception {
+
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withMessageTemplate(mock(MessageTemplate.class))
+                .withWaitForResponses(numberOfResponses)
+                .withResponseTimeout(respTimeout)
+                .withRoutingKey(routingKey)
+                .withAckTimeout(ackTimeout)
+                .build();
+
+        when(channelManagerMock.findOrCreateProducer(any(MessageDestination.class))).thenReturn(producerMock);
+
+        return setUpRequester(onResponse, onAcknowledge, onError, endHandler, requestOptions);
+    }
+
+    private RequesterImpl setUpRequester(BiConsumer onResponse, BiConsumer onAcknowledge, BiConsumer onError, Callback endHandler, RequestOptions requestOptions) {
         MsbContextImpl msbContext = TestUtils.createMsbContextBuilder()
                 .withChannelManager(channelManagerMock)
                 .build();
 
-        RequesterImpl requester = spy(RequesterImpl.create(NAMESPACE, requestOptionsMock, msbContext, new TypeReference() {
+        RequesterImpl requester = spy(RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() {
         }));
         requester.onResponse(onResponse)
                 .onError(onError)
                 .onAcknowledge(onAcknowledge)
                 .onEnd(endHandler);
 
-        collectorMock = spy(new Collector<>(NAMESPACE, TestUtils.createMsbRequestMessageNoPayload(NAMESPACE), requestOptionsMock, msbContext, requester.eventHandlers,
+        collectorMock = spy(new Collector<>(NAMESPACE, TestUtils.createMsbRequestMessageNoPayload(NAMESPACE), requestOptions, msbContext, requester.eventHandlers,
                 new TypeReference() {
                 }));
 
         doReturn(collectorMock)
                 .when(requester)
                 .createCollector(anyString(), any(Message.class), any(RequestOptions.class), any(MsbContextImpl.class), any(), anyBoolean());
-
         return requester;
     }
-
 }

From b7095f58f08e4e93eb59b2f3165463871ad12b7f Mon Sep 17 00:00:00 2001
From: alex 
Date: Wed, 31 Aug 2016 14:42:02 +0300
Subject: [PATCH 148/226] Added example for routing keys usage

---
 examples/pom.xml                              | 16 +++++
 .../msb/examples/ConsumerWithRoutingKeys.java | 32 +++++++++
 .../msb/examples/ProducerWithRoutingKey.java  | 69 +++++++++++++++++++
 3 files changed, 117 insertions(+)
 create mode 100644 examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java
 create mode 100644 examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java

diff --git a/examples/pom.xml b/examples/pom.xml
index ac6115d7..695cbb09 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -23,6 +23,16 @@
         https://github.com/tcdl
     
 
+    
+        
+            
+                ch.qos.logback
+                logback-classic
+                1.1.7
+            
+        
+    
+
     
         
             io.github.tcdl.msb
@@ -32,6 +42,12 @@
             io.github.tcdl.msb
             msb-java-amqp
         
+
+        
+            ch.qos.logback
+            logback-classic
+        
+
     
 
 
\ No newline at end of file
diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java b/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java
new file mode 100644
index 00000000..a41acf66
--- /dev/null
+++ b/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java
@@ -0,0 +1,32 @@
+package io.github.tcdl.msb.examples;
+
+import com.google.common.collect.Sets;
+import io.github.tcdl.msb.api.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Run this consumer in conjunction with {@link ProducerWithRoutingKey} to see that only messages sent with
+ * routing keys 'zero' and 'two' are consumed and the messages sent with routing key 'one' are not.
+ */
+public class ConsumerWithRoutingKeys {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ConsumerWithRoutingKeys.class);
+
+    public static void main(String[] args) {
+        MsbContext msbContext = new MsbContextBuilder().
+                enableShutdownHook(true).
+                build();
+
+        MessageTemplate messageTemplate = new MessageTemplate();
+
+        ObjectFactory objectFactory = msbContext.getObjectFactory();
+        ResponderServer responderServer = objectFactory.createResponderServer("routing:namespace",
+                Sets.newHashSet("zero", "two"),
+                messageTemplate,
+                (request, responderContext) -> {
+                    LOG.info("Received message: {}", request);
+                });
+        responderServer.listen();
+    }
+}
diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java b/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java
new file mode 100644
index 00000000..172ccf50
--- /dev/null
+++ b/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java
@@ -0,0 +1,69 @@
+package io.github.tcdl.msb.examples;
+
+import io.github.tcdl.msb.api.MessageTemplate;
+import io.github.tcdl.msb.api.MsbContext;
+import io.github.tcdl.msb.api.MsbContextBuilder;
+import io.github.tcdl.msb.api.RequestOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Run this consumer in conjunction with {@link ConsumerWithRoutingKeys} to see that only messages sent with
+ * routing keys 'zero' and 'two' are consumed and the messages sent with routing key 'one' are not.
+ */
+public class ProducerWithRoutingKey {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ProducerWithRoutingKey.class);
+
+    public static void main(String[] args) {
+        MsbContext msbContext = new MsbContextBuilder().
+                enableShutdownHook(true).
+                build();
+
+        Map tikToRoutingKey = new HashMap<>(3);
+        tikToRoutingKey.put(0, "zero");
+        tikToRoutingKey.put(1, "one");
+        tikToRoutingKey.put(2, "two");
+
+        MessageTemplate messageTemplate = new MessageTemplate().withTags("publish-with-routing-key");
+
+        AtomicInteger tik = new AtomicInteger(0);
+
+        Runnable task = () -> {
+
+            String routingKey = tikToRoutingKey.get(tik.getAndIncrement() % 3);
+            RequestOptions requestOptions = new RequestOptions.Builder()
+                    .withRoutingKey(routingKey)
+                    .withMessageTemplate(messageTemplate)
+                    .build();
+
+            LOG.info("Sending message with routing key '{}'", routingKey);
+            msbContext.getObjectFactory()
+                    .createRequester("routing:namespace", requestOptions, String.class)
+                    .publish(routingKey.toUpperCase(), UUID.randomUUID().toString());
+        };
+
+        runEachNSeconds(2, task);
+    }
+
+    private static void runEachNSeconds(int secondsNumber, Runnable task) {
+        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                executor.shutdownNow();
+            }
+        });
+
+        executor.scheduleAtFixedRate(task, 0, secondsNumber, TimeUnit.SECONDS);
+    }
+}

From ae1a4a1b18eeb5ecc59ba35804cac70c72c2a1d1 Mon Sep 17 00:00:00 2001
From: alex 
Date: Tue, 6 Sep 2016 19:26:42 +0300
Subject: [PATCH 149/226] Added 'routingKey' to message envelope

---
 .../bdd/steps/RequesterResponderSteps.java    | 63 +++++++++++++++++--
 .../scenarios/requester_responder.story       |  3 +-
 .../resources/scenarios/routing_keys.story    | 22 ++++++-
 .../java/io/github/tcdl/msb/Producer.java     |  7 +--
 .../github/tcdl/msb/api/MessageTemplate.java  | 14 +++++
 .../io/github/tcdl/msb/api/ObjectFactory.java | 13 +++-
 .../github/tcdl/msb/api/RequestOptions.java   |  8 ---
 .../github/tcdl/msb/api/message/Topics.java   | 21 ++++++-
 .../github/tcdl/msb/collector/Collector.java  |  4 +-
 .../tcdl/msb/impl/ObjectFactoryImpl.java      | 16 ++++-
 .../github/tcdl/msb/impl/RequesterImpl.java   | 54 +++++++++-------
 .../tcdl/msb/message/MessageFactory.java      | 14 ++++-
 .../java/io/github/tcdl/msb/ProducerTest.java |  2 +-
 .../tcdl/msb/impl/RequesterImplTest.java      | 50 +++++++++------
 .../tcdl/msb/message/MessageFactoryTest.java  | 19 +++++-
 .../objectfactory/TestMsbObjectFactory.java   | 10 +++
 release-notes.html                            |  5 ++
 17 files changed, 244 insertions(+), 81 deletions(-)

diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 003c23ce..4885318a 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -8,9 +8,12 @@
 import io.github.tcdl.msb.api.MessageTemplate;
 import io.github.tcdl.msb.api.MsbContext;
 import io.github.tcdl.msb.api.Requester;
+import io.github.tcdl.msb.api.message.Message;
+import io.github.tcdl.msb.api.message.Topics;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.support.Utils;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.hamcrest.Matchers;
 import org.jbehave.core.annotations.Given;
 import org.jbehave.core.annotations.Then;
@@ -25,6 +28,7 @@
 import java.util.stream.Collectors;
 
 import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -54,7 +58,7 @@ public class RequesterResponderSteps extends MsbSteps {
     private final String PAYLOAD = "PAYLOAD";
     private final int ACK_TIMEOUT = 500;
     private final Map> receivedMessagesByConsumer = new ConcurrentHashMap<>();
-
+    private final Deque rawIncomingMessages = new ConcurrentLinkedDeque<>();
 
     public Optional getDefaultRequestsAckType() {
         return defaultRequestsAckType;
@@ -199,7 +203,7 @@ public void createRequester(String namespace) {
     }
 
     // requester steps
-    @Given("requester sets forwarding to $forwardNamespace and sends requests to namespace $namespace")
+    @Given("requester sets forwarding to $forwardNamespace and target namespace to $namespace")
     public void createRequesterWithForwarding(String forwardNamespace, String namespace) {
         requester = helper.createRequester(DEFAULT_CONTEXT_NAME, namespace, forwardNamespace, 0, RestPayload.class);
     }
@@ -264,6 +268,15 @@ public void subscribeResponder(String responderId, String namespace, List {
+                    rawIncomingMessages.add(responderContext.getOriginalMessage());
+                }, String.class).listen();
+    }
+
     @Then("$responderId responder receives only messages $messages")
     public void assertReceivedMessages(String responderId, List expectedMessagesRaw) throws InterruptedException {
 
@@ -344,17 +357,44 @@ public void responseEquals(ExamplesTable table) throws Exception {
 
     @Then("request forward namespace equals $forwardNamespace")
     public void responseEquals(String forwardNamespace) throws Exception {
-        Assert.assertEquals(forwardNamespace, latestForwardNamespace);
+        assertEquals(forwardNamespace, latestForwardNamespace);
+    }
+
+
+    @Then("message envelope topics section is $table")
+    public void checkTopicsSection(ExamplesTable table) throws InterruptedException {
+        TimeUnit.SECONDS.sleep(1);
+        Message lastMessage = rawIncomingMessages.pollLast();
+
+        Map expected = table.getRow(0);
+        String expectedTo = normalizeNull(expected.get("to"));
+        String expectedForward = normalizeNull(expected.get("forward"));
+        String expectedResponse = normalizeNull(expected.get("response"));
+        String expectedRoutingKey = normalizeNull(expected.get("routingKey"));
+
+        Topics topics = lastMessage.getTopics();
+        assertEquals("Invalid value of topic 'to'", expectedTo, topics.getTo());
+        assertEquals("Invalid value of topic 'forward'", expectedForward, topics.getForward());
+        assertEquals("Invalid value of topic 'response'", expectedResponse, topics.getResponse());
+        assertEquals("Invalid value of field 'routingKey'", expectedRoutingKey, topics.getRoutingKey());
+    }
+
+    private String normalizeNull(String string){
+        if(string !=null && string.trim().equalsIgnoreCase("null")){
+            return null;
+        } else {
+            return string;
+        }
     }
 
     @Then("responder requests received count equals $expectedRequestsReceivedCount")
     public void requestCountEquals(int expectedCountRequestsReceived) throws Exception {
-        Assert.assertEquals(expectedCountRequestsReceived, countRequestsReceived.get());
+        assertEquals(expectedCountRequestsReceived, countRequestsReceived.get());
     }
 
     @Then("requester responses received count equals $expectedRequestsReceivedCount")
     public void responseCountEquals(int expectedCountResponsesReceived) throws Exception {
-        Assert.assertEquals(expectedCountResponsesReceived, countResponsesReceived.get());
+        assertEquals(expectedCountResponsesReceived, countResponsesReceived.get());
     }
 
     @Then("response contains $table")
@@ -386,6 +426,19 @@ public void requestForSingleResult(String namespace, String body, String routing
                 .publish(body);
     }
 
+    @When("requester sends to $namespace a request with forward namespace $forwardNamespace, body '$body' and routing key $routingKey")
+    public void publishWithRoutingKey(String namespace, String forwardNamespace, String body, String routingKey) throws Exception {
+        helper.initDefault();
+        helper.getContext(DEFAULT_CONTEXT_NAME).getObjectFactory()
+                .createRequesterForFireAndForget(namespace, new MessageDestination(forwardNamespace, routingKey), new MessageTemplate())
+                .publish(body);
+    }
+
+    @When("requester sends to $namespace a request with forward namespace $forwardNamespace, body '$body' without routing key")
+    public void publishWithoutRoutingKey(String namespace, String forwardNamespace, String body) throws Exception {
+        publishWithRoutingKey(namespace, forwardNamespace, body, StringUtils.EMPTY);
+    }
+
     @When("requester blocks waiting for response for $timeout ms")
     public void blockUntilResponseReceived(int timeout) throws Exception {
         resolvedResponse = lastFutureResult.get(timeout, TimeUnit.MILLISECONDS);
diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story
index 8554c24a..e01d4dc5 100644
--- a/acceptance/src/test/resources/scenarios/requester_responder.story
+++ b/acceptance/src/test/resources/scenarios/requester_responder.story
@@ -8,7 +8,6 @@ After:
 Outcome: ANY
 Then shutdown MSB
 
-
 Scenario: Sends a request to a responder server and waits for response
 
 Given responder server responds with '{"result": "hello jbehave"}'
@@ -128,7 +127,7 @@ Scenario: Message forwarding
 
 Given responder server responds with '{"result": "hello jbehave - forwarding"}'
 And responder server listens on namespace test:jbehave
-And requester sets forwarding to test:jbehave:forwarding and sends requests to namespace test:jbehave
+And requester sets forwarding to test:jbehave:forwarding and target namespace to test:jbehave
 When requester sends a request
 Then requester gets response in 5000 ms
 And responder requests received count equals 1
diff --git a/acceptance/src/test/resources/scenarios/routing_keys.story b/acceptance/src/test/resources/scenarios/routing_keys.story
index 14ca6ad5..31f2f136 100644
--- a/acceptance/src/test/resources/scenarios/routing_keys.story
+++ b/acceptance/src/test/resources/scenarios/routing_keys.story
@@ -1,7 +1,27 @@
+Lifecycle:
+Before:
+Given start MSB
+After:
+Outcome: ANY
+Then shutdown MSB
+
+Scenario: consumers receive messages according to routing keys
 Given 1st responder server listens on namespace test:namespace with routing keys routing-key-1, routing-key-2
 Given 2nd responder server listens on namespace test:namespace with routing keys routing-key-3
 When requester sends to test:namespace a request with body '{"messageId":"rk1"}' and routing key routing-key-1
 When requester sends to test:namespace a request with body '{"messageId":"rk2"}' and routing key routing-key-2
 When requester sends to test:namespace a request with body '{"messageId":"rk3"}' and routing key routing-key-3
 Then 1st responder receives only messages '{"messageId":"rk1"}', '{"messageId":"rk2"}'
-Then 2nd responder receives only messages '{"messageId":"rk3"}'
\ No newline at end of file
+Then 2nd responder receives only messages '{"messageId":"rk3"}'
+
+Scenario: routing key is ignored for messages that require forwarding
+Given responder server listens on fanout namespace test:routing
+And requester sets forwarding to test:routing:forwarding and target namespace to test:routing
+When requester sends to test:routing a request with forward namespace test:routing:forwarding, body '{"messageId":"rk1"}' and routing key routing-key-1
+Then message envelope topics section is
+|to          |forward                |response|routingKey   |
+|test:routing|test:routing:forwarding|null    |routing-key-1|
+When requester sends to test:routing a request with forward namespace test:routing:forwarding, body '{"messageId":"rk2"}' without routing key
+Then message envelope topics section is
+|to          |forward                |response|routingKey|
+|test:routing|test:routing:forwarding|null    |          |
\ No newline at end of file
diff --git a/core/src/main/java/io/github/tcdl/msb/Producer.java b/core/src/main/java/io/github/tcdl/msb/Producer.java
index 478129e0..5c0debc5 100644
--- a/core/src/main/java/io/github/tcdl/msb/Producer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Producer.java
@@ -36,10 +36,7 @@ public Producer(ProducerAdapter rawAdapter, String topic, Callback mess
     }
 
     public void publish(Message message) {
-        publish(message, null);
-    }
-
-    public void publish(Message message, String routingKey) {
+        String routingKey = message.getTopics().getRoutingKey();
         try {
             String jsonMessage = Utils.toJson(message, messageMapper);
             LOG.debug("Publishing message to adapter : {}", jsonMessage);
@@ -50,6 +47,4 @@ public void publish(Message message, String routingKey) {
             throw e;
         }
     }
-
-
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/api/MessageTemplate.java b/core/src/main/java/io/github/tcdl/msb/api/MessageTemplate.java
index 6d9bec5b..b7f78719 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/MessageTemplate.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/MessageTemplate.java
@@ -67,11 +67,25 @@ public MessageTemplate withTags(String... tags) {
         return  this;
     }
 
+    public void addTags(String... tags) {
+        this.tags.addAll(Arrays.asList(tags));
+    }
+
+    /**
+     * @deprecated because of misleading signature and complete duplication of {@link #withTags(String...)} logic.
+     * Method will be removed in version 1.7
+     * todo remove it
+     */
+    @Deprecated
     public MessageTemplate addTag(String... tags) {
         this.tags.addAll(Arrays.asList(tags));
         return  this;
     }
 
+    public void addTag(String tag) {
+        this.tags.add(tag);
+    }
+
     @Override
     public String toString() {
         return "MessageTemplate [ttl=" + ttl + ", tags=" + StringUtils.join(tags, ",") + "]";
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
index d5bcbb02..bb2ab320 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
@@ -6,11 +6,8 @@
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
 
 import java.lang.reflect.Type;
-import java.util.Collection;
 import java.util.Set;
 
-import static javafx.scene.input.KeyCode.T;
-
 /**
  * Provides methods for creation client-facing API classes.
  */
@@ -175,6 +172,16 @@ public Type getType() {
      */
      Requester createRequesterForFireAndForget(MessageDestination messageDestination, MessageTemplate messageTemplate);
 
+    /**
+     * Creates requester that doesn't wait for any responses or acknowledgments.
+     *
+     * @param namespace topic name to send a request to (where consumer with forwarding capabilities is expected)
+     * @param forwardTo {@link MessageDestination} to be used for forwarding
+     * @param messageTemplate {@link MessageTemplate} to be used
+     * @return new instance of a {@link Requester} with original message
+     */
+     Requester createRequesterForFireAndForget(String namespace, MessageDestination forwardTo, MessageTemplate messageTemplate);
+
     /**
      * @param namespace                 topic on a bus for listening on incoming requests
      * @param messageTemplate           template used for creating response messages
diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
index f72944bc..347e7b8a 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java
@@ -1,11 +1,6 @@
 package io.github.tcdl.msb.api;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.Validate;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
 
 /**
  * Specifies waiting policy (for acknowledgements and responses) for requests sent using {@link Requester}.
@@ -81,9 +76,6 @@ public String getRoutingKey() {
         return routingKey;
     }
 
-    public boolean hasRoutingKey(){
-        return StringUtils.isNotBlank(routingKey);
-    }
 
     @Override
     public String toString() {
diff --git a/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java b/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java
index 6bd41014..32b3ce00 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/message/Topics.java
@@ -1,21 +1,32 @@
 package io.github.tcdl.msb.api.message;
 
-import org.apache.commons.lang3.Validate;
 import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.Validate;
 
 public final class Topics {
 
     private final String to;
     private final String response;
     private final String forward;
+    private final String routingKey;
 
     @JsonCreator
-    public Topics(@JsonProperty("to") String to, @JsonProperty("response") String response,  @JsonProperty("forward") String forward) {
+    public Topics(@JsonProperty("to") String to,
+                  @JsonProperty("response") String response,
+                  @JsonProperty("forward") String forward,
+                  @JsonProperty("routingKey") String routingKey) {
+
         Validate.notNull(to, "the 'to' must not be null");
         this.to = to;
         this.response = response;
         this.forward = forward;
+        this.routingKey = routingKey;
+    }
+
+    public Topics(String to, String response, String forward) {
+        this(to, response, forward, null);
     }
 
     public String getTo() {
@@ -30,8 +41,12 @@ public String getForward() {
         return forward;
     }
 
+    public String getRoutingKey() {
+        return routingKey;
+    }
+
     @Override
     public String toString() {
-        return "Topics [to=" + to + ", response=" + response + ", forward=" + forward + "]";
+        return "Topics [to=" + to + ", response=" + response + ", forward=" + forward + ", routingKey=" + routingKey + "]";
     }
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
index f1d51c1c..22b7e3a6 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
@@ -273,9 +273,9 @@ private synchronized boolean isAllConsumedMessagesHandled() {
     }
 
     void processAck(Acknowledge acknowledge) {
-        if (acknowledge == null)
+        if (acknowledge == null) {
             return;
-
+        }
         if (acknowledge.getResponsesRemaining() != null) {
             LOG.debug("[correlation id: {}] Responses remaining for responderId [{}] is set to {}",
                     requestMessage.getCorrelationId(), acknowledge.getResponderId(),
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
index b95c9430..0f8a4ac2 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
@@ -1,13 +1,11 @@
 package io.github.tcdl.msb.impl;
 
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.JsonNode;
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.monitor.AggregatorStats;
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.monitor.aggregator.DefaultChannelMonitorAggregator;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -107,6 +105,20 @@ public  Requester createRequesterForFireAndForget(MessageDestination desti
         return RequesterImpl.create(destination.getTopic(), optionsBuilder.build(), msbContext, null);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public  Requester createRequesterForFireAndForget(String namespace, MessageDestination forwardTo, MessageTemplate messageTemplate) {
+        RequestOptions.Builder optionsBuilder = new RequestOptions.Builder()
+                .withMessageTemplate(messageTemplate)
+                .withForwardNamespace(forwardTo.getTopic())
+                .withRoutingKey(forwardTo.getRoutingKey())
+                .withWaitForResponses(0);
+
+        return RequesterImpl.create(namespace, optionsBuilder.build(), msbContext, null);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
index 0990d671..faa485fa 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
@@ -2,16 +2,18 @@
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import io.github.tcdl.msb.ChannelManager;
-import io.github.tcdl.msb.Producer;
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.Message;
+import io.github.tcdl.msb.api.message.Topics;
 import io.github.tcdl.msb.collector.Collector;
 import io.github.tcdl.msb.events.EventHandlers;
 import io.github.tcdl.msb.message.MessageFactory;
 import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
 
+import java.util.Arrays;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
 
@@ -130,7 +132,7 @@ public CompletableFuture request(Object requestPayload, Message originalMessa
                 })
                 .onError((exception, message) -> futureResult.cancel(true));
 
-        publish(true, requestOptions, requestPayload, originalMessage, tags);
+        publish(true, requestPayload, originalMessage, tags);
         return futureResult;
     }
 
@@ -139,49 +141,48 @@ public CompletableFuture request(Object requestPayload, Message originalMessa
      */
     @Override
     public void publish(Object requestPayload, Message originalMessage, String... tags) {
-        publish(false, requestOptions, requestPayload, originalMessage, tags);
+        publish(false, requestPayload, originalMessage, tags);
     }
 
-    private void publish(boolean invokeHandlersDirectly, RequestOptions requestOptions, Object requestPayload, Message originalMessage, String... tags) {
+    private void publish(boolean invokeHandlersDirectly, Object requestPayload, Message originalMessage, String... tags) {
         MessageTemplate messageTemplate = MessageTemplate.copyOf(requestOptions.getMessageTemplate());
 
-
         if (tags != null) {
-            for(String tag: tags) {
-                if(tag != null) {
-                    messageTemplate.addTag(tag);
-                }
-            }
+            Arrays.stream(tags).filter(tag -> tag != null).forEach(messageTemplate::addTag);
         }
+
         Message.Builder messageBuilder = messageFactory.createRequestMessageBuilder(
                 namespace,
                 requestOptions.getForwardNamespace(),
+                requestOptions.getRoutingKey(),
                 messageTemplate,
                 originalMessage);
 
         Message message = messageFactory.createRequestMessage(messageBuilder, requestPayload);
-        String topic = message.getTopics().getTo();
 
-        //use Collector instance to handle expected responses/acks
-        if (isWaitForAckMs() || isWaitForResponses()) {
+        boolean fireAndForget = !(isWaitForAckMs() || isWaitForResponses());
+        boolean forwardingRequired = StringUtils.isNotBlank(requestOptions.getForwardNamespace());
 
-            Collector collector = createCollector(message.getTopics().getResponse(), message, requestOptions, context, eventHandlers, invokeHandlersDirectly);
+        if(forwardingRequired || fireAndForget){
+            publishMessage(message);
+        } else {
+            //set up collector for responses or acks
+            Collector collector = createCollector(message, requestOptions, context, eventHandlers, invokeHandlersDirectly);
             collector.listenForResponses();
 
-            publishMessage(topic, requestOptions, message);
+            publishMessage(message);
 
             collector.waitForResponses();
-        } else {
-            publishMessage(topic, requestOptions, message);
         }
     }
 
-    private void publishMessage(String topic, RequestOptions requestOptions, Message message) {
-        if (requestOptions.hasRoutingKey()) {
-            MessageDestination destination = new MessageDestination(topic, requestOptions.getRoutingKey());
-            getChannelManager().findOrCreateProducer(destination).publish(message, requestOptions.getRoutingKey());
+    private void publishMessage(Message message) {
+        Topics topics = message.getTopics();
+        if (StringUtils.isBlank(topics.getForward()) && StringUtils.isNotBlank(topics.getRoutingKey())) {
+            MessageDestination destination = new MessageDestination(topics.getTo(), topics.getRoutingKey());
+            getChannelManager().findOrCreateProducer(destination).publish(message);
         } else {
-            getChannelManager().findOrCreateProducer(topic).publish(message);
+            getChannelManager().findOrCreateProducer(topics.getTo()).publish(message);
         }
     }
 
@@ -241,7 +242,12 @@ private ChannelManager getChannelManager() {
         return context.getChannelManager();
     }
 
-    Collector createCollector(String topic, Message requestMessage, RequestOptions requestOptions, MsbContextImpl context, EventHandlers eventHandlers, boolean invokeHandlersDirectly) {
-        return new Collector<>(topic, requestMessage, requestOptions, context, eventHandlers, payloadTypeReference, invokeHandlersDirectly);
+    Collector createCollector(Message requestMessage,
+                                 RequestOptions requestOptions,
+                                 MsbContextImpl context,
+                                 EventHandlers eventHandlers,
+                                 boolean invokeHandlersDirectly) {
+        return new Collector<>(requestMessage.getTopics().getResponse(), requestMessage, requestOptions, context,
+                eventHandlers, payloadTypeReference, invokeHandlersDirectly);
     }
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/message/MessageFactory.java b/core/src/main/java/io/github/tcdl/msb/message/MessageFactory.java
index da0c60bd..98b5ca1b 100644
--- a/core/src/main/java/io/github/tcdl/msb/message/MessageFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/message/MessageFactory.java
@@ -10,6 +10,7 @@
 import io.github.tcdl.msb.api.message.Topics;
 import io.github.tcdl.msb.config.ServiceDetails;
 import io.github.tcdl.msb.support.Utils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
 
 import java.time.Clock;
@@ -49,12 +50,19 @@ public Message createBroadcastMessage(Message.Builder messageBuilder, Object pay
         return createRequestMessage(messageBuilder, payload);
     }
 
-    public Message.Builder createRequestMessageBuilder(String namespace, String forwardNamespace, MessageTemplate messageTemplate, Message originalMessage) {
-        Topics topic = new Topics(namespace, namespace + ":response:" +
-                this.serviceDetails.getInstanceId(), forwardNamespace);
+    public Message.Builder createRequestMessageBuilder(String namespace, String forwardNamespace, String routingKey, MessageTemplate messageTemplate, Message originalMessage) {
+        String responseNamespace = StringUtils.isBlank(forwardNamespace)
+                ? namespace + ":response:" + this.serviceDetails.getInstanceId()
+                : null;
+
+        Topics topic = new Topics(namespace, responseNamespace, forwardNamespace, routingKey);
         return createMessageBuilder(topic, messageTemplate, originalMessage, false);
     }
 
+    public Message.Builder createRequestMessageBuilder(String namespace, String forwardNamespace, MessageTemplate messageTemplate, Message originalMessage) {
+        return createRequestMessageBuilder(namespace, forwardNamespace, null, messageTemplate, originalMessage);
+    }
+
     public Message.Builder createResponseMessageBuilder(MessageTemplate messageTemplate, Message originalMessage) {
         Topics topic = new Topics(originalMessage.getTopics().getResponse(), null, null);
         return createMessageBuilder(topic, messageTemplate, originalMessage, true);
diff --git a/core/src/test/java/io/github/tcdl/msb/ProducerTest.java b/core/src/test/java/io/github/tcdl/msb/ProducerTest.java
index 5c0d3705..da26896d 100644
--- a/core/src/test/java/io/github/tcdl/msb/ProducerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ProducerTest.java
@@ -83,7 +83,7 @@ public void testPublishVerifyHandlerCalled() {
     public void testPublishWithDefaultRoutingKey() throws Exception {
         Message originaMessage = TestUtils.createSimpleRequestMessage(TOPIC);
         Producer producer = new Producer(adapterMock, TOPIC, handlerMock, messageMapper);
-        producer.publish(originaMessage, null);
+        producer.publish(originaMessage);
         verify(adapterMock).publish(anyString(), eq(StringUtils.EMPTY));
     }
 
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
index 0edff2bb..28e9f63d 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
@@ -39,7 +39,8 @@
 @RunWith(MockitoJUnitRunner.class)
 public class RequesterImplTest {
 
-    private static final String NAMESPACE = "test:requester";
+    private static final String BROADCAST_NAMESPACE = "test:hello:all";
+    private static final String MULTICAST_NAMESPACE = "test:hello:everyone";
 
     @Mock
     private ChannelManager channelManagerMock;
@@ -83,7 +84,6 @@ public void testPublishWaitForResponses() throws Exception {
         verify(collectorMock, times(4)).waitForResponses();
     }
 
-
     @Test
     public void testPublishWithRoutingKeyWaitForResponses() throws Exception {
         RequesterImpl requester = initRequesterForResponsesWith("UK", 1, 0, 0, null, null, null, null);
@@ -352,7 +352,7 @@ public void testRequest_acknowledgeHandlerCancelsFutureOnTooManyResponses() thro
     public void testRequestMessage() throws Exception {
         ChannelManager channelManagerMock = mock(ChannelManager.class);
         Producer producerMock = mock(Producer.class);
-        when(channelManagerMock.findOrCreateProducer(NAMESPACE)).thenReturn(producerMock);
+        when(channelManagerMock.findOrCreateProducer(BROADCAST_NAMESPACE)).thenReturn(producerMock);
         ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
 
         MsbContextImpl msbContext = TestUtils.createMsbContextBuilder()
@@ -361,7 +361,7 @@ public void testRequestMessage() throws Exception {
                 .build();
 
         RestPayload requestPayload = TestUtils.createSimpleRequestPayload();
-        Requester requester = RequesterImpl.create(NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference(){});
+        Requester requester = RequesterImpl.create(BROADCAST_NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference(){});
         requester.publish(requestPayload);
         verify(producerMock).publish(messageArgumentCaptor.capture());
 
@@ -375,7 +375,7 @@ public void testRequestMessage() throws Exception {
     public void testRequestMessageWithTags() throws Exception {
         ChannelManager channelManagerMock = mock(ChannelManager.class);
         Producer producerMock = mock(Producer.class);
-        when(channelManagerMock.findOrCreateProducer(NAMESPACE)).thenReturn(producerMock);
+        when(channelManagerMock.findOrCreateProducer(BROADCAST_NAMESPACE)).thenReturn(producerMock);
         ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
 
         MsbContextImpl msbContext = TestUtils.createMsbContextBuilder()
@@ -390,7 +390,7 @@ public void testRequestMessageWithTags() throws Exception {
         RestPayload requestPayload = TestUtils.createSimpleRequestPayload();
         RequestOptions requestOptions = TestUtils.createSimpleRequestOptionsWithTags(tag);
 
-        Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference(){});
+        Requester requester = RequesterImpl.create(BROADCAST_NAMESPACE, requestOptions, msbContext, new TypeReference(){});
         requester.publish(requestPayload, dynamicTag1, dynamicTag2, nullTag);
         verify(producerMock).publish(messageArgumentCaptor.capture());
 
@@ -399,11 +399,12 @@ public void testRequestMessageWithTags() throws Exception {
     }
 
     @Test
-    public void testRequestMessageWithForward() throws Exception {
-        String forwardNamespace = "test:forward";
+    public void testRequestMessageWithForward_shouldNotWaitForResponsesOrAcks() throws Exception {
+        String routingKey = "to.santa";
+
         ChannelManager channelManagerMock = mock(ChannelManager.class);
         Producer producerMock = mock(Producer.class);
-        when(channelManagerMock.findOrCreateProducer(NAMESPACE)).thenReturn(producerMock);
+        when(channelManagerMock.findOrCreateProducer(BROADCAST_NAMESPACE)).thenReturn(producerMock);
         ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
 
         MsbContextImpl msbContext = TestUtils.createMsbContextBuilder()
@@ -414,15 +415,24 @@ public void testRequestMessageWithForward() throws Exception {
         RestPayload requestPayload = TestUtils.createSimpleRequestPayload();
         RequestOptions requestOptions = new RequestOptions
                 .Builder()
-                .withForwardNamespace(forwardNamespace).build();
+                .withRoutingKey(routingKey)
+                .withWaitForResponses(3)
+                .withAckTimeout(1)
+                .withForwardNamespace(MULTICAST_NAMESPACE).build();
+
+        RequesterImpl requesterSpy = spy(RequesterImpl.create(BROADCAST_NAMESPACE, requestOptions, msbContext, new TypeReference() {}));
+        requesterSpy.publish(requestPayload);
 
-        Requester requester = RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() {
-        });
-        requester.publish(requestPayload);
         verify(producerMock).publish(messageArgumentCaptor.capture());
 
+        //check collector wasn't set up
+        verify(requesterSpy, never()).createCollector(any(), any(), any(), any(), anyBoolean());
+        verify(channelManagerMock, never()).findOrCreateProducer(any(MessageDestination.class));
+
+        //check message fields
         Message requestMessage = messageArgumentCaptor.getValue();
-        assertEquals(forwardNamespace, requestMessage.getTopics().getForward());
+        assertEquals(MULTICAST_NAMESPACE, requestMessage.getTopics().getForward());
+        assertEquals(routingKey, requestMessage.getTopics().getRoutingKey());
     }
 
     private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout, Integer ackTimeout,
@@ -439,7 +449,7 @@ private RequesterImpl initRequesterForResponsesWith(Integer numberO
 
         when(channelManagerMock.findOrCreateProducer(anyString())).thenReturn(producerMock);
 
-        return setUpRequester(onResponse, onAcknowledge, onError, endHandler, requestOptions);
+        return setUpRequester(BROADCAST_NAMESPACE, onResponse, onAcknowledge, onError, endHandler, requestOptions);
     }
 
     private RequesterImpl initRequesterForResponsesWith(String routingKey, Integer numberOfResponses, Integer respTimeout, Integer ackTimeout,
@@ -457,28 +467,28 @@ private RequesterImpl initRequesterForResponsesWith(String routingK
 
         when(channelManagerMock.findOrCreateProducer(any(MessageDestination.class))).thenReturn(producerMock);
 
-        return setUpRequester(onResponse, onAcknowledge, onError, endHandler, requestOptions);
+        return setUpRequester(MULTICAST_NAMESPACE, onResponse, onAcknowledge, onError, endHandler, requestOptions);
     }
 
-    private RequesterImpl setUpRequester(BiConsumer onResponse, BiConsumer onAcknowledge, BiConsumer onError, Callback endHandler, RequestOptions requestOptions) {
+    private RequesterImpl setUpRequester(String namespace, BiConsumer onResponse, BiConsumer onAcknowledge, BiConsumer onError, Callback endHandler, RequestOptions requestOptions) {
         MsbContextImpl msbContext = TestUtils.createMsbContextBuilder()
                 .withChannelManager(channelManagerMock)
                 .build();
 
-        RequesterImpl requester = spy(RequesterImpl.create(NAMESPACE, requestOptions, msbContext, new TypeReference() {
+        RequesterImpl requester = spy(RequesterImpl.create(namespace, requestOptions, msbContext, new TypeReference() {
         }));
         requester.onResponse(onResponse)
                 .onError(onError)
                 .onAcknowledge(onAcknowledge)
                 .onEnd(endHandler);
 
-        collectorMock = spy(new Collector<>(NAMESPACE, TestUtils.createMsbRequestMessageNoPayload(NAMESPACE), requestOptions, msbContext, requester.eventHandlers,
+        collectorMock = spy(new Collector<>(namespace, TestUtils.createMsbRequestMessageNoPayload(namespace), requestOptions, msbContext, requester.eventHandlers,
                 new TypeReference() {
                 }));
 
         doReturn(collectorMock)
                 .when(requester)
-                .createCollector(anyString(), any(Message.class), any(RequestOptions.class), any(MsbContextImpl.class), any(), anyBoolean());
+                .createCollector(any(Message.class), any(RequestOptions.class), any(MsbContextImpl.class), any(), anyBoolean());
         return requester;
     }
 }
diff --git a/core/src/test/java/io/github/tcdl/msb/message/MessageFactoryTest.java b/core/src/test/java/io/github/tcdl/msb/message/MessageFactoryTest.java
index a2f912f9..ac9a557e 100644
--- a/core/src/test/java/io/github/tcdl/msb/message/MessageFactoryTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/message/MessageFactoryTest.java
@@ -141,7 +141,24 @@ public void testCreateRequestMessageBuilderWithForward() {
 
         assertNotNull(message.getCorrelationId());
         assertThat(message.getTopics().getTo(), is(namespace));
-        assertThat(message.getTopics().getResponse(), notNullValue());
+        assertThat(message.getTopics().getResponse(), nullValue());
+        assertThat(message.getTopics().getRoutingKey(), nullValue());
+        assertThat(message.getTopics().getForward(), is(forwardNamespace));
+    }
+
+    @Test
+    public void testCreateRequestMessageBuilderRoutingKey() {
+        String namespace = "test:request-builder";
+        String routingKey = "banana.key";
+        String forwardNamespace = "test:forward";
+
+        Builder requestMessageBuilder = messageFactory.createRequestMessageBuilder(namespace, forwardNamespace, routingKey, messageOptions, null);
+        Message message = requestMessageBuilder.build();
+
+        assertNotNull(message.getCorrelationId());
+        assertThat(message.getTopics().getTo(), is(namespace));
+        assertThat(message.getTopics().getResponse(), nullValue());
+        assertThat(message.getTopics().getRoutingKey(), is(routingKey));
         assertThat(message.getTopics().getForward(), is(forwardNamespace));
     }
 
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
index 562958dc..817e87e5 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
@@ -135,6 +135,16 @@ public  Requester createRequesterForFireAndForget(MessageDestination desti
         return createRequester(destination.getTopic(), requestOptions, (Class)null);
     }
 
+    @Override
+    public  Requester createRequesterForFireAndForget(String namespace, MessageDestination forwardTo, MessageTemplate messageTemplate) {
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(0)
+                .withForwardNamespace(forwardTo.getTopic())
+                .withRoutingKey(forwardTo.getRoutingKey())
+                .build();
+        return createRequester(namespace, requestOptions, (Class)null);
+    }
+
     @Override
     public PayloadConverter getPayloadConverter() {
         return null;
diff --git a/release-notes.html b/release-notes.html
index cd359683..81831068 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -9,6 +9,11 @@ 

Welcome to MSB-Java version 1.5.1

July 29, 2016

+Features of MSB-Java version 1.5.2:
+   - Added routing key support to API and AMQP adapters
+   - Added "routingKey" field to message envelope "topics" section
+   - Removed confusing response topic for cases when no responses/acks are expected
+
 Features of MSB-Java version 1.5.1:
    - Added stop() method to ResponderServer
    - Add request object and map into MsbThreadContext

From 351d900f5796f3a0eb5c08a460ef8e91b8dfd324 Mon Sep 17 00:00:00 2001
From: alex 
Date: Wed, 7 Sep 2016 11:05:16 +0300
Subject: [PATCH 150/226] Updated documentation and schema.json

---
 .../java/io/github/tcdl/msb/api/ObjectFactory.java    | 10 ++++++++++
 .../io/github/tcdl/msb/impl/ObjectFactoryImpl.java    | 10 ++++++++++
 core/src/main/resources/schema.json                   |  3 ++-
 .../msb/mock/objectfactory/TestMsbObjectFactory.java  | 11 +++++++++++
 doc/MSB.md                                            |  5 ++++-
 5 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
index bb2ab320..d5a62dd9 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
@@ -163,6 +163,16 @@ public Type getType() {
      */
      Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate);
 
+    /**
+     * Creates requester that doesn't wait for any responses or acknowledgments
+     *
+     * @param namespace topic to send a request to
+     * @param forwardTo topic to be used for forwarding
+     * @param messageTemplate {@link MessageTemplate} to be used
+     * @return new instance of a {@link Requester} with original message
+     */
+     Requester createRequesterForFireAndForget(String namespace, String forwardTo, MessageTemplate messageTemplate);
+
     /**
      * Creates requester that doesn't wait for any responses or acknowledgments
      *
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
index 0f8a4ac2..33e5850b 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java
@@ -92,6 +92,16 @@ public  Requester createRequesterForFireAndForget(String namespace, Messag
         return RequesterImpl.create(namespace, optionsBuilder.build(), msbContext, null);
     }
 
+    @Override
+    public  Requester createRequesterForFireAndForget(String namespace, String forwardTo, MessageTemplate messageTemplate) {
+        RequestOptions.Builder optionsBuilder = new RequestOptions.Builder()
+                .withForwardNamespace(forwardTo)
+                .withMessageTemplate(messageTemplate)
+                .withWaitForResponses(0);
+
+        return RequesterImpl.create(namespace, optionsBuilder.build(), msbContext, null);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/core/src/main/resources/schema.json b/core/src/main/resources/schema.json
index 5e18852f..700d8f25 100644
--- a/core/src/main/resources/schema.json
+++ b/core/src/main/resources/schema.json
@@ -15,7 +15,8 @@
       "properties": {
         "to": { "$ref": "#/definitions/topic" },
         "response": { "$ref": "#/definitions/topic" },
-        "forward": { "$ref": "#/definitions/topic" }
+        "forward": { "$ref": "#/definitions/topic" },
+        "routingKey": {"type": "string"}
       },
       "required": ["to"]
     },
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
index 817e87e5..5de65bb8 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java
@@ -5,6 +5,7 @@
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.monitor.AggregatorStats;
 import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
+import io.github.tcdl.msb.impl.RequesterImpl;
 
 import java.lang.reflect.Type;
 import java.util.Set;
@@ -126,6 +127,16 @@ public  Requester createRequesterForFireAndForget(String namespace, Messag
         return createRequester(namespace, requestOptions, (Class)null);
     }
 
+    @Override
+    public  Requester createRequesterForFireAndForget(String namespace, String forwardTo, MessageTemplate messageTemplate) {
+        RequestOptions.Builder optionsBuilder = new RequestOptions.Builder()
+                .withForwardNamespace(forwardTo)
+                .withMessageTemplate(messageTemplate)
+                .withWaitForResponses(0);
+
+        return createRequester(namespace, optionsBuilder.build(), (Class)null);
+    }
+
     @Override
     public  Requester createRequesterForFireAndForget(MessageDestination destination, MessageTemplate messageTemplate) {
         RequestOptions requestOptions = new RequestOptions.Builder()
diff --git a/doc/MSB.md b/doc/MSB.md
index 204b3732..aeb1e200 100644
--- a/doc/MSB.md
+++ b/doc/MSB.md
@@ -59,7 +59,9 @@ Here's an example of a message:
   "correlationId": "3c19407acf3218000003598f",
   "topics": {
     "to": "test:aggregator",
-    "response": "test:aggregator:response:3c19407acf32180000016402"
+    "response": "test:aggregator:response:3c19407acf32180000016402",
+    "forward": "test:proxy",
+    "routingKey": "to.santa.claus"
   },
   "meta": {
     "ttl": null,
@@ -100,6 +102,7 @@ topics                  | section for routing information
   to                    | name of the topic this message is sent to
   response              | name of the topic where response to this message is expected
   forward               | name of the topic for a message forwarding
+  routingKey            | routing key that was used when message was published or should be used for forwarding
 meta                    | section for message meta information
   ttl                   | time to live of a message. If ttl is exceeded an incoming message is ignored
   createdAt             | timezone-aware date/time when message was created

From c1d4596171ecf0d17c794fbd89a45d1acf6e323f Mon Sep 17 00:00:00 2001
From: sergiv83 
Date: Wed, 21 Sep 2016 18:06:36 +0300
Subject: [PATCH 151/226] msb spring boot auto configuration (draft)

---
 .../tcdl/msb/api/MsbContextBuilder.java       |  15 +-
 msb-spring-boot-starter/pom.xml               |  80 ++++++
 .../tcdl/msb/MsbConfigAutoConfiguration.java  |  80 ++++++
 .../tcdl/msb/MsbContextAutoConfiguration.java |  41 +++
 .../io/github/tcdl/msb/MsbProperties.java     | 233 ++++++++++++++++++
 .../main/resources/META-INF/spring.factories  |   2 +
 .../src/main/resources/defaultMsbConfig.conf  |  35 +++
 pom.xml                                       |   1 +
 8 files changed, 484 insertions(+), 3 deletions(-)
 create mode 100644 msb-spring-boot-starter/pom.xml
 create mode 100644 msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbConfigAutoConfiguration.java
 create mode 100644 msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbContextAutoConfiguration.java
 create mode 100644 msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbProperties.java
 create mode 100644 msb-spring-boot-starter/src/main/resources/META-INF/spring.factories
 create mode 100644 msb-spring-boot-starter/src/main/resources/defaultMsbConfig.conf

diff --git a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
index c4419511..a92ed5db 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
@@ -35,6 +35,7 @@ public class MsbContextBuilder {
     private static final Logger LOG = LoggerFactory.getLogger(MsbContextBuilder.class);
 
     private Config config;
+    private MsbConfig msbConfig;
     private boolean enableShutdownHook;
     private boolean enableChannelMonitorAgent;
     private ObjectMapper payloadMapper = createMessageEnvelopeMapper();
@@ -54,6 +55,12 @@ public MsbContextBuilder withConfig(Config config) {
         return this;
     }
 
+    public MsbContextBuilder withMsbConfig(MsbConfig config) {
+        this.msbConfig = config;
+        return this;
+    }
+
+
     /**
      * Provide a custom {@link MessageGroupStrategy} instance in order to process messages with the same groupId
      * in a single-threaded mode.
@@ -110,10 +117,12 @@ public MsbContextBuilder withPayloadMapper(ObjectMapper payloadMapper) {
     public MsbContext build() {
         Clock clock = Clock.systemDefaultZone();
         JsonValidator validator = new JsonValidator();
-        if (config == null) {
-            config = ConfigFactory.load();
+        if (msbConfig == null) {
+            if (config == null) {
+                config = ConfigFactory.load();
+            }
+            msbConfig = new MsbConfig(config);
         }
-        MsbConfig msbConfig = new MsbConfig(config);
         ObjectMapper messageEnvelopeMapper = createMessageEnvelopeMapper();
 
         AdapterFactory adapterFactory = new AdapterFactoryLoader(msbConfig).getAdapterFactory();
diff --git a/msb-spring-boot-starter/pom.xml b/msb-spring-boot-starter/pom.xml
new file mode 100644
index 00000000..42f13918
--- /dev/null
+++ b/msb-spring-boot-starter/pom.xml
@@ -0,0 +1,80 @@
+
+
+	4.0.0
+	io.github.tcdl.msb
+	msb-spring-boot-starter
+	msb spring boot starter
+	0.0.1-SNAPSHOT
+	jar
+
+	
+		UTF-8
+		UTF-8
+
+		1.8
+		1.8
+
+		1.5.2-SNAPSHOT 
+	
+
+	
+		
+			org.springframework.boot
+			spring-boot-autoconfigure
+		
+
+		
+			io.github.tcdl.msb
+			msb-java-core
+			${msb.version}
+		
+
+		
+			io.github.tcdl.msb
+			msb-java-amqp
+			${msb.version}
+		
+
+		
+		
+			org.springframework.boot
+			spring-boot-configuration-processor
+			true
+		
+	
+
+	
+		 
+			
+				org.springframework.boot
+				spring-boot-dependencies
+				1.4.0.RELEASE
+				pom
+				import
+			
+		
+	
+
+	
+		
+			spring-milestones
+			Spring Milestones
+			https://repo.spring.io/milestone
+			
+				false
+			
+		
+	
+	
+		
+			spring-milestones
+			Spring Milestones
+			https://repo.spring.io/milestone
+			
+				false
+			
+		
+	
+
+
diff --git a/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbConfigAutoConfiguration.java b/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbConfigAutoConfiguration.java
new file mode 100644
index 00000000..3122cc41
--- /dev/null
+++ b/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbConfigAutoConfiguration.java
@@ -0,0 +1,80 @@
+package io.github.tcdl.msb;
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigValueFactory;
+import io.github.tcdl.msb.config.MsbConfig;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@AutoConfigureBefore(MsbContextAutoConfiguration.class)
+@EnableConfigurationProperties(MsbProperties.class)
+public class MsbConfigAutoConfiguration {
+
+    @Autowired
+    MsbProperties msbProperties;
+
+    @Bean
+    public MsbConfig config() {
+        Config config = ConfigFactory.load("defaultMsbConfig");
+        config = config.withValue("msbConfig.serviceDetails.name", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.name));
+        config = config.withValue("msbConfig.serviceDetails.instanceId", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.instanceId));
+
+        // Service Details
+        if (StringUtils.isNoneBlank(msbProperties.serviceDetails.version))
+            config = config.withValue("msbConfig.serviceDetails.version", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.version));
+        if (StringUtils.isNoneBlank(msbProperties.serviceDetails.hostname))
+            config = config.withValue("msbConfig.serviceDetails.version", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.hostname));
+        if (StringUtils.isNoneBlank(msbProperties.serviceDetails.ip))
+            config = config.withValue("msbConfig.serviceDetails.ip", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.ip));
+        if (msbProperties.serviceDetails.pid != null)
+            config = config.withValue("msbConfig.serviceDetails.pid", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.pid));
+
+        // Broker Adapter Factory
+        if (StringUtils.isNotBlank(msbProperties.brokerAdapterFactory))
+            config = config.withValue("msbConfig.brokerAdapterFactory", ConfigValueFactory.fromAnyRef(msbProperties.brokerAdapterFactory));
+
+        // Thread pool used for scheduling ack and response timeout tasks
+        if (msbProperties.timerThreadPoolSize != null)
+            config = config.withValue("msbConfig.timerThreadPoolSize", ConfigValueFactory.fromAnyRef(msbProperties.timerThreadPoolSize));
+
+        // Threading Config for Clients
+        if (msbProperties.consumerThreadPoolSize != null)
+            config = config.withValue("msbConfig.threadingConfig.consumerThreadPoolSize", ConfigValueFactory.fromAnyRef(msbProperties.consumerThreadPoolSize));
+        if (msbProperties.consumerThreadPoolQueueCapacity != null)
+            config = config.withValue("msbConfig.threadingConfig.consumerThreadPoolQueueCapacity", ConfigValueFactory.fromAnyRef(msbProperties.consumerThreadPoolQueueCapacity));
+
+        // Enable/disable message validation against json schema
+        if (msbProperties.validateMessage != null)
+            config = config.withValue("msbConfig.validateMessage", ConfigValueFactory.fromAnyRef(msbProperties.validateMessage));
+
+        //Broker Adapter Defaults
+        if (StringUtils.isNotBlank(msbProperties.brokerConfig.host))
+            config = config.withValue("msbConfig.brokerConfig.host", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.host));
+        if (StringUtils.isNotBlank(msbProperties.brokerConfig.port))
+            config = config.withValue("msbConfig.brokerConfig.port", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.port));
+        if (StringUtils.isNotBlank(msbProperties.brokerConfig.userName))
+            config = config.withValue("msbConfig.brokerConfig.username", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.userName));
+        if (StringUtils.isNotBlank(msbProperties.brokerConfig.virtualHost))
+            config = config.withValue("msbConfig.brokerConfig.virtualHost", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.virtualHost));
+        if (msbProperties.brokerConfig.useSSL != null)
+            config = config.withValue("msbConfig.brokerConfig.useSSL", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.useSSL));
+        if (msbProperties.brokerConfig.durable != null)
+            config = config.withValue("msbConfig.brokerConfig.durable", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.durable));
+        if (msbProperties.brokerConfig.charset != null)
+            config = config.withValue("msbConfig.brokerConfig.charset", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.charset));
+        if (StringUtils.isNotBlank(msbProperties.brokerConfig.groupId))
+            config = config.withValue("msbConfig.brokerConfig.groupId", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.groupId));
+        if (msbProperties.brokerConfig.heartbeatIntervalSec != null)
+            config = config.withValue("msbConfig.brokerConfig.heartbeatIntervalSec", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.heartbeatIntervalSec));
+        if (msbProperties.brokerConfig.networkRecoveryIntervalMs != null)
+            config = config.withValue("msbConfig.brokerConfig.networkRecoveryIntervalMs", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.networkRecoveryIntervalMs));
+
+        return new MsbConfig(config);
+    }
+}
diff --git a/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbContextAutoConfiguration.java b/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbContextAutoConfiguration.java
new file mode 100644
index 00000000..9d890966
--- /dev/null
+++ b/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbContextAutoConfiguration.java
@@ -0,0 +1,41 @@
+package io.github.tcdl.msb;
+
+import io.github.tcdl.msb.api.MessageTemplate;
+import io.github.tcdl.msb.api.MsbContext;
+import io.github.tcdl.msb.api.MsbContextBuilder;
+import io.github.tcdl.msb.config.MsbConfig;
+import io.github.tcdl.msb.threading.MessageGroupStrategy;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MsbContextAutoConfiguration {
+
+    @Autowired
+    MsbConfig msbConfig;
+
+    @Autowired(required = false)
+    MessageGroupStrategy messageGroupStrategy;
+
+    @Bean
+    @ConditionalOnMissingBean(MessageTemplate.class)
+    public MessageTemplate messageTemplate() {
+        return new MessageTemplate();
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(MsbContext.class)
+    public MsbContext msbContext() {
+
+        MsbContextBuilder builder = new MsbContextBuilder()
+                .enableChannelMonitorAgent(true)
+                .withMsbConfig(msbConfig);
+
+        if (messageGroupStrategy != null)
+            builder = builder.withMessageGroupStrategy(messageGroupStrategy);
+
+        return builder.build();
+    }
+}
diff --git a/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbProperties.java b/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbProperties.java
new file mode 100644
index 00000000..8fcb171e
--- /dev/null
+++ b/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbProperties.java
@@ -0,0 +1,233 @@
+package io.github.tcdl.msb;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.nio.charset.Charset;
+
+@ConfigurationProperties("msbConfig")
+public class MsbProperties {
+
+    ServiceDetails serviceDetails = new ServiceDetails();
+    String brokerAdapterFactory;
+    Integer timerThreadPoolSize;
+    Boolean validateMessage;
+    Integer consumerThreadPoolSize;
+    Integer consumerThreadPoolQueueCapacity;
+    BrokerConfig brokerConfig = new BrokerConfig();
+
+    public ServiceDetails getServiceDetails() {
+        return serviceDetails;
+    }
+
+    public void setServiceDetails(ServiceDetails serviceDetails) {
+        this.serviceDetails = serviceDetails;
+    }
+
+    public String getBrokerAdapterFactory() {
+        return brokerAdapterFactory;
+    }
+
+    public void setBrokerAdapterFactory(String brokerAdapterFactory) {
+        this.brokerAdapterFactory = brokerAdapterFactory;
+    }
+
+    public Integer getTimerThreadPoolSize() {
+        return timerThreadPoolSize;
+    }
+
+    public void setTimerThreadPoolSize(Integer timerThreadPoolSize) {
+        this.timerThreadPoolSize = timerThreadPoolSize;
+    }
+
+    public Boolean getValidateMessage() {
+        return validateMessage;
+    }
+
+    public void setValidateMessage(Boolean validateMessage) {
+        this.validateMessage = validateMessage;
+    }
+
+    public Integer getConsumerThreadPoolSize() {
+        return consumerThreadPoolSize;
+    }
+
+    public void setConsumerThreadPoolSize(Integer consumerThreadPoolSize) {
+        this.consumerThreadPoolSize = consumerThreadPoolSize;
+    }
+
+    public Integer getConsumerThreadPoolQueueCapacity() {
+        return consumerThreadPoolQueueCapacity;
+    }
+
+    public void setConsumerThreadPoolQueueCapacity(Integer consumerThreadPoolQueueCapacity) {
+        this.consumerThreadPoolQueueCapacity = consumerThreadPoolQueueCapacity;
+    }
+
+    public BrokerConfig getBrokerConfig() {
+        return brokerConfig;
+    }
+
+    public void setBrokerConfig(BrokerConfig brokerConfig) {
+        this.brokerConfig = brokerConfig;
+    }
+
+    public class ServiceDetails {
+        String name;
+        String version;
+        String instanceId;
+        String hostname;
+        String ip;
+        Long pid;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        public void setVersion(String version) {
+            this.version = version;
+        }
+
+        public String getInstanceId() {
+            return instanceId;
+        }
+
+        public void setInstanceId(String instanceId) {
+            this.instanceId = instanceId;
+        }
+
+        public String getHostname() {
+            return hostname;
+        }
+
+        public void setHostname(String hostname) {
+            this.hostname = hostname;
+        }
+
+        public String getIp() {
+            return ip;
+        }
+
+        public void setIp(String ip) {
+            this.ip = ip;
+        }
+
+        public Long getPid() {
+            return pid;
+        }
+
+        public void setPid(Long pid) {
+            this.pid = pid;
+        }
+    }
+
+    public class BrokerConfig {
+        Charset charset;
+        String host;
+        String port;
+        String userName;
+        String password;
+        String virtualHost;
+        Boolean useSSL;
+        String groupId;
+        Boolean durable;
+        Integer heartbeatIntervalSec;
+        Long networkRecoveryIntervalMs;
+
+        public Charset getCharset() {
+            return charset;
+        }
+
+        public void setCharset(Charset charset) {
+            this.charset = charset;
+        }
+
+        public String getHost() {
+            return host;
+        }
+
+        public void setHost(String host) {
+            this.host = host;
+        }
+
+        public String getPort() {
+            return port;
+        }
+
+        public void setPort(String port) {
+            this.port = port;
+        }
+
+        public String getUserName() {
+            return userName;
+        }
+
+        public void setUserName(String userName) {
+            this.userName = userName;
+        }
+
+        public String getPassword() {
+            return password;
+        }
+
+        public void setPassword(String password) {
+            this.password = password;
+        }
+
+        public String getVirtualHost() {
+            return virtualHost;
+        }
+
+        public void setVirtualHost(String virtualHost) {
+            this.virtualHost = virtualHost;
+        }
+
+        public Boolean getUseSSL() {
+            return useSSL;
+        }
+
+        public void setUseSSL(Boolean useSSL) {
+            this.useSSL = useSSL;
+        }
+
+        public String getGroupId() {
+            return groupId;
+        }
+
+        public void setGroupId(String groupId) {
+            this.groupId = groupId;
+        }
+
+        public Boolean getDurable() {
+            return durable;
+        }
+
+        public void setDurable(Boolean durable) {
+            this.durable = durable;
+        }
+
+        public Integer getHeartbeatIntervalSec() {
+            return heartbeatIntervalSec;
+        }
+
+        public void setHeartbeatIntervalSec(Integer heartbeatIntervalSec) {
+            this.heartbeatIntervalSec = heartbeatIntervalSec;
+        }
+
+        public Long getNetworkRecoveryIntervalMs() {
+            return networkRecoveryIntervalMs;
+        }
+
+        public void setNetworkRecoveryIntervalMs(Long networkRecoveryIntervalMs) {
+            this.networkRecoveryIntervalMs = networkRecoveryIntervalMs;
+        }
+    }
+
+}
diff --git a/msb-spring-boot-starter/src/main/resources/META-INF/spring.factories b/msb-spring-boot-starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..4bd0214c
--- /dev/null
+++ b/msb-spring-boot-starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.github.tcdl.msb.MsbContextAutoConfiguration,\
+io.github.tcdl.msb.MsbConfigAutoConfiguration
\ No newline at end of file
diff --git a/msb-spring-boot-starter/src/main/resources/defaultMsbConfig.conf b/msb-spring-boot-starter/src/main/resources/defaultMsbConfig.conf
new file mode 100644
index 00000000..bd872236
--- /dev/null
+++ b/msb-spring-boot-starter/src/main/resources/defaultMsbConfig.conf
@@ -0,0 +1,35 @@
+msbConfig {
+
+  # Service Details
+  serviceDetails = {
+    name = ${?SERVICE_NAME}
+
+    instanceId = ${?SERVICE_INSTANCE_ID}
+
+    version = "1.0.0"
+  }
+
+  brokerAdapterFactory = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory"
+
+  # Thread pool used for scheduling ack and response timeout tasks
+  timerThreadPoolSize = 2
+
+  threadingConfig = {
+    consumerThreadPoolSize = 5
+    consumerThreadPoolQueueCapacity = -1
+  }
+
+  # Enable/disable message validation against json schema
+  validateMessage = false
+
+  # Broker Adapter Defaults
+  brokerConfig = {
+     #    host = "127.0.0.1"
+     #    port = "5672"
+     #    username = "user"
+     #    password = "p@ssw0rd"
+     #    virtualHost = ""
+     #    useSSL = "false"
+    durable = true
+  }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 89d730d7..2827a39f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,7 @@
         acceptance
         jmeter
         examples
+        msb-spring-boot-starter
     
 
     

From ede8d95ecfd16b2e8584ccd2e3c636678ee1c732 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Mon, 3 Oct 2016 13:17:35 +0300
Subject: [PATCH 152/226] msb-java 1.5.2 release

---
 release-notes.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/release-notes.html b/release-notes.html
index 81831068..29a6acd4 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -4,9 +4,9 @@
 
 
 
-

Welcome to MSB-Java version 1.5.1

+

Welcome to MSB-Java version 1.5.2

-

July 29, 2016

+

October 3, 2016

 Features of MSB-Java version 1.5.2:

From 4d7d6f2609a68f8f75057bfd44929b09ae828920 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Mon, 3 Oct 2016 13:21:22 +0300
Subject: [PATCH 153/226] [maven-release-plugin] prepare release msb-java-1.5.2

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 jmeter/pom.xml     | 8 +++-----
 pom.xml            | 4 ++--
 7 files changed, 15 insertions(+), 17 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index ea2b14c6..8d491b14 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2-SNAPSHOT
+        1.5.2
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.2
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index a96f129e..6a02e0a9 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2-SNAPSHOT
+        1.5.2
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.2
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 92ff32fc..d72d529b 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2-SNAPSHOT
+        1.5.2
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.2
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 5a83c822..0d1f8ca4 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2-SNAPSHOT
+        1.5.2
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.2
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 695cbb09..8b8cd24a 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2-SNAPSHOT
+        1.5.2
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.2
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 1a78488f..cf23409b 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -1,14 +1,12 @@
 
-
+
 
     4.0.0
 
     
         io.github.tcdl.msb
         msb-java
-        1.5.2-SNAPSHOT
+        1.5.2
         ../pom.xml
     
 
@@ -19,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.2
     
 
     
diff --git a/pom.xml b/pom.xml
index 89d730d7..cb483a15 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.5.2-SNAPSHOT
+    1.5.2
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.5.2
     
     
         

From 352fb74afe97ccf8d91a5d4c28fa8685729b872b Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Mon, 3 Oct 2016 13:21:29 +0300
Subject: [PATCH 154/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml | 4 ++--
 amqp/pom.xml       | 4 ++--
 cli/pom.xml        | 4 ++--
 core/pom.xml       | 4 ++--
 examples/pom.xml   | 4 ++--
 jmeter/pom.xml     | 4 ++--
 pom.xml            | 4 ++--
 7 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 8d491b14..3e4dfe74 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2
+        1.5.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.2
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 6a02e0a9..564e365a 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2
+        1.5.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.2
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index d72d529b..eaebce95 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2
+        1.5.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.2
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 0d1f8ca4..e62043bf 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2
+        1.5.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.2
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 8b8cd24a..8c56bcb5 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2
+        1.5.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.2
+        HEAD
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index cf23409b..23f54a57 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2
+        1.5.3-SNAPSHOT
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.2
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index cb483a15..2c57b24f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.5.2
+    1.5.3-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.5.2
+        HEAD
     
     
         

From 7563c81c1c57e75e31899ff1cb755b0ca2205243 Mon Sep 17 00:00:00 2001
From: alex 
Date: Fri, 7 Oct 2016 15:17:59 +0300
Subject: [PATCH 155/226] AMQP explicit exchange type support

---
 .../bdd/steps/RequesterResponderSteps.java    |  47 +++--
 .../scenarios/blocking_requester.story        |   1 -
 .../resources/scenarios/routing_keys.story    |  18 +-
 .../msb/adapters/amqp/AmqpAdapterFactory.java |  57 ++++--
 .../adapters/amqp/AmqpConsumerAdapter.java    |  49 ++---
 .../adapters/amqp/AmqpProducerAdapter.java    |  29 +--
 .../tcdl/msb/api/AmqpRequestOptions.java      |  48 +++++
 .../tcdl/msb/api/AmqpResponderOptions.java    |  55 ++++++
 .../io/github/tcdl/msb/api/ExchangeType.java  |  11 ++
 .../msb/config/amqp/AmqpBrokerConfig.java     |  12 +-
 amqp/src/main/resources/amqp.conf             |   4 +
 .../adapters/amqp/AmqpAdapterFactoryTest.java |  51 +++--
 .../amqp/AmqpConsumerAdapterTest.java         |  35 ++--
 .../amqp/AmqpProducerAdapterTest.java         |  17 +-
 .../api/AmqpResponderOptionsBuilderTest.java  |  16 ++
 .../msb/config/amqp/AmqpBrokerConfigTest.java |   6 +
 .../io/github/tcdl/msb/ChannelManager.java    | 156 ++++-----------
 .../java/io/github/tcdl/msb/Consumer.java     |   9 +-
 .../tcdl/msb/adapters/AdapterFactory.java     |  27 ++-
 .../tcdl/msb/api/MessageDestination.java      |  51 -----
 .../io/github/tcdl/msb/api/ObjectFactory.java | 179 ++++++++----------
 .../github/tcdl/msb/api/RequestOptions.java   |  22 ++-
 .../github/tcdl/msb/api/ResponderOptions.java |  60 ++++++
 .../exception/AdapterCreationException.java   |  11 ++
 .../github/tcdl/msb/collector/Collector.java  |  10 +-
 .../tcdl/msb/impl/ObjectFactoryImpl.java      | 102 ++--------
 .../github/tcdl/msb/impl/RequesterImpl.java   |   8 +-
 .../github/tcdl/msb/impl/ResponderImpl.java   |   3 +-
 .../tcdl/msb/impl/ResponderServerImpl.java    |  23 +--
 .../agent/DefaultChannelMonitorAgent.java     |   3 +-
 .../msb/ChannelManagerConcurrentTest.java     |   9 +-
 .../github/tcdl/msb/ChannelManagerTest.java   | 109 ++++-------
 .../java/io/github/tcdl/msb/ConsumerTest.java |   7 -
 .../tcdl/msb/api/RequesterResponderIT.java    |   8 +-
 .../tcdl/msb/collector/CollectorTest.java     |   6 +-
 .../tcdl/msb/impl/RequesterImplTest.java      | 104 ++--------
 .../tcdl/msb/impl/ResponderImplTest.java      |   6 +-
 .../msb/impl/ResponderServerImplTest.java     |  61 +++---
 .../adapterfactory/TestMsbAdapterFactory.java |  26 +--
 .../objectfactory/TestMsbObjectFactory.java   | 105 +---------
 .../agent/DefaultChannelMonitorAgentTest.java |  17 +-
 .../msb/examples/ConsumerWithRoutingKeys.java |  12 +-
 .../msb/examples/ProducerWithRoutingKey.java  |   8 +-
 43 files changed, 709 insertions(+), 889 deletions(-)
 create mode 100644 amqp/src/main/java/io/github/tcdl/msb/api/AmqpRequestOptions.java
 create mode 100644 amqp/src/main/java/io/github/tcdl/msb/api/AmqpResponderOptions.java
 create mode 100644 amqp/src/main/java/io/github/tcdl/msb/api/ExchangeType.java
 create mode 100644 amqp/src/test/java/io/github/tcdl/msb/api/AmqpResponderOptionsBuilderTest.java
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/api/ResponderOptions.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/api/exception/AdapterCreationException.java

diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 4885318a..563de7e0 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -4,10 +4,7 @@
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigValueFactory;
-import io.github.tcdl.msb.api.MessageDestination;
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.MsbContext;
-import io.github.tcdl.msb.api.Requester;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.Topics;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
@@ -15,6 +12,7 @@
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.hamcrest.Matchers;
+import org.jbehave.core.annotations.BeforeScenario;
 import org.jbehave.core.annotations.Given;
 import org.jbehave.core.annotations.Then;
 import org.jbehave.core.annotations.When;
@@ -28,9 +26,7 @@
 import java.util.stream.Collectors;
 
 import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 
 /**
  * Steps to send requests and respond with predefined responses
@@ -254,7 +250,7 @@ public void sendRequestWithBody(String body) throws Exception {
         helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse, this::onEnd);
     }
 
-    @Given("$responderId responder server listens on namespace $namespace with routing keys $routingKeys")
+    @Given("$responderId responder server listens on namespace $namespace with binding keys $routingKeys")
     public void subscribeResponder(String responderId, String namespace, List routingKeys) {
         //modify name to make library generate different queue names for different consumers (responders)
         Config config = ConfigFactory.load()
@@ -262,7 +258,13 @@ public void subscribeResponder(String responderId, String namespace, List(routingKeys), new MessageTemplate(),
+
+        ResponderOptions amqpResponderOptions = new AmqpResponderOptions.Builder()
+                .withBindingKeys(new HashSet<>(routingKeys))
+                .withExchangeType(ExchangeType.TOPIC)
+                .build();
+
+        context.getObjectFactory().createResponderServer(namespace, amqpResponderOptions,
                 (request, responderContext) -> {
                     receivedMessagesByConsumer.computeIfAbsent(responderId, key -> new LinkedList<>()).add(request);
                 }, String.class).listen();
@@ -379,8 +381,8 @@ public void checkTopicsSection(ExamplesTable table) throws InterruptedException
         assertEquals("Invalid value of field 'routingKey'", expectedRoutingKey, topics.getRoutingKey());
     }
 
-    private String normalizeNull(String string){
-        if(string !=null && string.trim().equalsIgnoreCase("null")){
+    private String normalizeNull(String string) {
+        if (string != null && string.trim().equalsIgnoreCase("null")) {
             return null;
         } else {
             return string;
@@ -421,16 +423,26 @@ public void requestForSingleResult(String namespace) throws Exception {
     @When("requester sends to $namespace a request with body '$body' and routing key $routingKey")
     public void requestForSingleResult(String namespace, String body, String routingKey) throws Exception {
         helper.initDefault();
+        RequestOptions requestOptions = new AmqpRequestOptions.Builder()
+                .withExchangeType(ExchangeType.TOPIC)
+                .withRoutingKey(routingKey).build();
+
         helper.getContext(DEFAULT_CONTEXT_NAME).getObjectFactory()
-                .createRequesterForFireAndForget(new MessageDestination(namespace, routingKey), new MessageTemplate())
+                .createRequesterForFireAndForget(namespace, requestOptions)
                 .publish(body);
     }
 
     @When("requester sends to $namespace a request with forward namespace $forwardNamespace, body '$body' and routing key $routingKey")
     public void publishWithRoutingKey(String namespace, String forwardNamespace, String body, String routingKey) throws Exception {
         helper.initDefault();
+
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withForwardNamespace(forwardNamespace)
+                .withRoutingKey(routingKey)
+                .build();
+
         helper.getContext(DEFAULT_CONTEXT_NAME).getObjectFactory()
-                .createRequesterForFireAndForget(namespace, new MessageDestination(forwardNamespace, routingKey), new MessageTemplate())
+                .createRequesterForFireAndForget(namespace, requestOptions)
                 .publish(body);
     }
 
@@ -480,8 +492,13 @@ public void assertExceptionOccured() throws Exception {
         fail("Expected exception not thrown");
     }
 
-    @Then("reset mock responses")
-    public void  resetMockResponses(){
+//    @Then("reset mock responses")
+//    public void resetMockResponses() {
+//        responses.clear();
+//    }
+
+    @BeforeScenario
+    public void resetMockResponses() {
         responses.clear();
     }
 }
diff --git a/acceptance/src/test/resources/scenarios/blocking_requester.story b/acceptance/src/test/resources/scenarios/blocking_requester.story
index a2bf434b..d8c41843 100644
--- a/acceptance/src/test/resources/scenarios/blocking_requester.story
+++ b/acceptance/src/test/resources/scenarios/blocking_requester.story
@@ -3,7 +3,6 @@ Before:
 Given MSB configuration with consumer thread pool size 1
 And start MSB
 And clear log
-And reset mock responses
 After:
 Outcome: ANY
 Then shutdown MSB
diff --git a/acceptance/src/test/resources/scenarios/routing_keys.story b/acceptance/src/test/resources/scenarios/routing_keys.story
index 31f2f136..a24f1639 100644
--- a/acceptance/src/test/resources/scenarios/routing_keys.story
+++ b/acceptance/src/test/resources/scenarios/routing_keys.story
@@ -6,22 +6,10 @@ Outcome: ANY
 Then shutdown MSB
 
 Scenario: consumers receive messages according to routing keys
-Given 1st responder server listens on namespace test:namespace with routing keys routing-key-1, routing-key-2
-Given 2nd responder server listens on namespace test:namespace with routing keys routing-key-3
+Given 1st responder server listens on namespace test:namespace with binding keys routing-key-1, routing-key-2
+Given 2nd responder server listens on namespace test:namespace with binding keys routing-key-3
 When requester sends to test:namespace a request with body '{"messageId":"rk1"}' and routing key routing-key-1
 When requester sends to test:namespace a request with body '{"messageId":"rk2"}' and routing key routing-key-2
 When requester sends to test:namespace a request with body '{"messageId":"rk3"}' and routing key routing-key-3
 Then 1st responder receives only messages '{"messageId":"rk1"}', '{"messageId":"rk2"}'
-Then 2nd responder receives only messages '{"messageId":"rk3"}'
-
-Scenario: routing key is ignored for messages that require forwarding
-Given responder server listens on fanout namespace test:routing
-And requester sets forwarding to test:routing:forwarding and target namespace to test:routing
-When requester sends to test:routing a request with forward namespace test:routing:forwarding, body '{"messageId":"rk1"}' and routing key routing-key-1
-Then message envelope topics section is
-|to          |forward                |response|routingKey   |
-|test:routing|test:routing:forwarding|null    |routing-key-1|
-When requester sends to test:routing a request with forward namespace test:routing:forwarding, body '{"messageId":"rk2"}' without routing key
-Then message envelope topics section is
-|to          |forward                |response|routingKey|
-|test:routing|test:routing:forwarding|null    |          |
\ No newline at end of file
+Then 2nd responder receives only messages '{"messageId":"rk3"}'
\ No newline at end of file
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
index b9b62c74..6c931375 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
@@ -7,27 +7,26 @@
 import com.typesafe.config.ConfigFactory;
 import io.github.tcdl.msb.adapters.AdapterFactory;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
-import io.github.tcdl.msb.adapters.ProducerAdapter;
-import io.github.tcdl.msb.api.MessageDestination;
+import io.github.tcdl.msb.api.*;
+import io.github.tcdl.msb.api.exception.AdapterCreationException;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
+import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
 import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.*;
+import java.util.concurrent.TimeoutException;
 
 /**
  * AmqpAdapterFactory is an implementation of {@link AdapterFactory}
  * for {@link AmqpProducerAdapter} and {@link AmqpConsumerAdapter}
  */
 public class AmqpAdapterFactory implements AdapterFactory {
+
     private static final Logger LOG = LoggerFactory.getLogger(AmqpAdapterFactory.class);
 
     private volatile AmqpBrokerConfig amqpBrokerConfig;
@@ -45,6 +44,7 @@ public void init(MsbConfig msbConfig) {
         connectionManager = createConnectionManager(connection);
     }
 
+    //TODO extract config loading from this class and then rewrite unit test for this class completely
     protected AmqpBrokerConfig createAmqpBrokerConfig(MsbConfig msbConfig) {
         Config amqpApplicationConfig = msbConfig.getBrokerConfig();
         Config amqpLibConfig = ConfigFactory.load("amqp").getConfig("config.amqp");
@@ -61,23 +61,48 @@ protected AmqpBrokerConfig createAmqpBrokerConfig(MsbConfig msbConfig) {
     }
     
     @Override
-    public ProducerAdapter createProducerAdapter(String topic) {
-        return new AmqpProducerAdapter(topic, amqpBrokerConfig, connectionManager);
-    }
+    public AmqpProducerAdapter createProducerAdapter(String topic, RequestOptions requestOptions) {
+        Validate.notNull(topic, "topic is mandatory");
+        Validate.notNull(requestOptions, "requestOptions are mandatory");
+
+        Class requestOptionsClass = requestOptions.getClass();
+        ExchangeType exchangeType;
+
+        if (AmqpRequestOptions.class.isAssignableFrom(requestOptionsClass)) {
+            exchangeType = ((AmqpRequestOptions) requestOptions).getExchangeType();
+        } else if (requestOptionsClass.equals(RequestOptions.class)) {
+            exchangeType = amqpBrokerConfig.getDefaultExchangeType();
+        } else {
+            throw new AdapterCreationException("Illegal for this AdapterFactory RequestOptions subclass");
+        }
 
-    @Override
-    public ProducerAdapter createProducerAdapter(MessageDestination destination) {
-        return new AmqpProducerAdapter(destination, amqpBrokerConfig, connectionManager);
+        return new AmqpProducerAdapter(topic, exchangeType, amqpBrokerConfig, connectionManager);
     }
 
     @Override
-    public ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
-        return new AmqpConsumerAdapter(topic, amqpBrokerConfig, connectionManager, isResponseTopic);
+    public AmqpConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
+        return new AmqpConsumerAdapter(topic, amqpBrokerConfig.getDefaultExchangeType(),
+                ResponderOptions.DEFAULTS.getBindingKeys(),
+                amqpBrokerConfig, connectionManager, isResponseTopic);
     }
 
     @Override
-    public ConsumerAdapter createConsumerAdapter(String topic, Set routingKeys) {
-        return new AmqpConsumerAdapter(topic, routingKeys, amqpBrokerConfig, connectionManager);
+    public AmqpConsumerAdapter createConsumerAdapter(String topic, ResponderOptions responderOptions, boolean isResponseTopic) {
+        Validate.notEmpty(topic, "topic is mandatory");
+        Validate.notNull(responderOptions, "responderOptions are mandatory");
+
+        Class responderOptionsClass = responderOptions.getClass();
+        ExchangeType exchangeType;
+
+        if (AmqpResponderOptions.class.isAssignableFrom(responderOptionsClass)) {
+            exchangeType = ((AmqpResponderOptions) responderOptions).getExchangeType();
+        } else if (responderOptionsClass.equals(ResponderOptions.class)) {
+            exchangeType = amqpBrokerConfig.getDefaultExchangeType();
+        } else {
+            throw new AdapterCreationException("Illegal for this AdapterFactory ResponderOptions subclass");
+        }
+
+        return new AmqpConsumerAdapter(topic, exchangeType, responderOptions.getBindingKeys(), amqpBrokerConfig, connectionManager, isResponseTopic);
     }
 
     protected ConnectionFactory createConnectionFactory(AmqpBrokerConfig adapterConfig) {
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
index 0a0788e0..4c44ecd9 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
@@ -1,60 +1,41 @@
 package io.github.tcdl.msb.adapters.amqp;
 
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-import com.google.common.collect.Lists;
-import com.rabbitmq.client.AMQP;
 import com.rabbitmq.client.Channel;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
-import io.github.tcdl.msb.api.MessageDestination;
+import io.github.tcdl.msb.api.ExchangeType;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import io.github.tcdl.msb.support.Utils;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Set;
+
 
 public class AmqpConsumerAdapter implements ConsumerAdapter {
 
     private Channel channel;
     private final String exchangeName;
-    private final Set routingKeys;
+    private final Set bindingKeys;
     private String consumerTag;
     private AmqpBrokerConfig adapterConfig;
     private boolean isResponseTopic = false;
 
-    /**
-     * The constructor.
-     *
-     * @param exchangeName - an exchange name associated with the adapter
-     * @throws ChannelException if adapter failed to create channel and declare exchange
-     */
-    public AmqpConsumerAdapter(String exchangeName, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager, boolean isResponseTopic) {
-        this(exchangeName, "fanout", Collections.singleton(StringUtils.EMPTY), amqpBrokerConfig, connectionManager, isResponseTopic);
-    }
-
-    public AmqpConsumerAdapter(String topic, Set routingKeys, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
-        this(topic, "topic", routingKeys, amqpBrokerConfig, connectionManager, false);
-    }
-
-    private AmqpConsumerAdapter(String exchangeName, String exchangeType, Set routingKeys, AmqpBrokerConfig amqpBrokerConfig,
-                                AmqpConnectionManager connectionManager, boolean isResponseTopic) {
+    public AmqpConsumerAdapter(String exchangeName, ExchangeType exchangeType, Set bindingKeys, AmqpBrokerConfig amqpBrokerConfig,
+                               AmqpConnectionManager connectionManager, boolean isResponseTopic) {
 
         Validate.notNull(exchangeName, "Exchange name is required");
-        Validate.notEmpty(routingKeys, "At least one routing key is required");
+        Validate.notNull(exchangeType, "Exchange type is required");
+        Validate.notEmpty(bindingKeys, "At least one routing key is required");
 
-        this.routingKeys = routingKeys;
+        this.bindingKeys = bindingKeys;
         this.exchangeName = exchangeName;
         this.adapterConfig = amqpBrokerConfig;
         this.isResponseTopic = isResponseTopic;
 
         try {
             channel = connectionManager.obtainConnection().createChannel();
-            channel.exchangeDeclare(exchangeName, exchangeType, false /* durable */, true /* auto-delete */, null);
+            channel.exchangeDeclare(exchangeName, exchangeType.value(), false /* durable */, true /* auto-delete */, null);
         } catch (IOException e) {
             throw new ChannelException("Failed to setup channel", e);
         }
@@ -74,12 +55,12 @@ public void subscribe(RawMessageHandler msgHandler) {
         try {
             channel.queueDeclare(queueName, durable /* durable */, false /* exclusive */, !durable /*auto-delete */, null);
             channel.basicQos(prefetchCount); // Don't accept more messages if we have any unacknowledged
-            for(String routingKey: routingKeys) {
-                channel.queueBind(queueName, exchangeName, routingKey);
+            for(String bindingKey : bindingKeys) {
+                channel.queueBind(queueName, exchangeName, bindingKey);
             }
             consumerTag = channel.basicConsume(queueName, false /* autoAck */, new AmqpMessageConsumer(channel, msgHandler, adapterConfig));
         } catch (IOException e) {
-            throw new ChannelException(String.format("Failed to subscribe to topic %s with routing keys %s", exchangeName, routingKeys), e);
+            throw new ChannelException(String.format("Failed to subscribe to topic %s with routing keys %s", exchangeName, bindingKeys), e);
         }
     }
 
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
index 39909b24..f1a27b2d 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
@@ -2,7 +2,7 @@
 
 import com.rabbitmq.client.MessageProperties;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
-import io.github.tcdl.msb.api.MessageDestination;
+import io.github.tcdl.msb.api.ExchangeType;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import org.apache.commons.lang3.StringUtils;
@@ -12,32 +12,23 @@
 import java.nio.charset.Charset;
 
 public class AmqpProducerAdapter implements ProducerAdapter {
-    private String exchangeName;
-    private AmqpBrokerConfig amqpBrokerConfig;
-    private AmqpAutoRecoveringChannel amqpAutoRecoveringChannel;
 
-    /**
-     * The constructor.
-     * @param topic - a topic name associated with the adapter
-     * @throws ChannelException if some problems during setup channel from RabbitMQ connection were occurred
-     */
-    public AmqpProducerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
-        this(topic, "fanout", amqpBrokerConfig, connectionManager);
-    }
-
-    public AmqpProducerAdapter(MessageDestination destination, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
-        this(destination.getTopic(), "topic", amqpBrokerConfig, connectionManager);
-    }
+    final String exchangeName;
+    final AmqpBrokerConfig amqpBrokerConfig;
+    final AmqpAutoRecoveringChannel amqpAutoRecoveringChannel;
 
-    public AmqpProducerAdapter(String topic, String exchangeType, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
-        Validate.notNull(topic, "the 'topic' must not be null");
+    public AmqpProducerAdapter(String topic, ExchangeType exchangeType, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
+        Validate.notNull(topic, "Topic is mandatory");
+        Validate.notNull(exchangeType, "Exchange type is mandatory");
+        Validate.notNull(amqpBrokerConfig, "Broker config is mandatory");
+        Validate.notNull(exchangeType, "Connection manager is mandatory");
 
         this.exchangeName = topic;
         this.amqpBrokerConfig = amqpBrokerConfig;
         this.amqpAutoRecoveringChannel = new AmqpAutoRecoveringChannel(connectionManager);
 
         try {
-            amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, exchangeType, false /* durable */, true /* auto-delete */, null);
+            amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, exchangeType.value(), false /* durable */, true /* auto-delete */, null);
         } catch (IOException e) {
             throw new ChannelException("Failed to setup channel from ActiveMQ connection", e);
         }
diff --git a/amqp/src/main/java/io/github/tcdl/msb/api/AmqpRequestOptions.java b/amqp/src/main/java/io/github/tcdl/msb/api/AmqpRequestOptions.java
new file mode 100644
index 00000000..9fe5830f
--- /dev/null
+++ b/amqp/src/main/java/io/github/tcdl/msb/api/AmqpRequestOptions.java
@@ -0,0 +1,48 @@
+package io.github.tcdl.msb.api;
+
+import org.apache.commons.lang3.Validate;
+
+import javax.annotation.Nonnull;
+
+
+public class AmqpRequestOptions extends RequestOptions {
+
+    private final ExchangeType exchangeType;
+
+    private AmqpRequestOptions(Integer ackTimeout,
+                               Integer responseTimeout,
+                               Integer waitForResponses,
+                               MessageTemplate messageTemplate,
+                               String forwardNamespace,
+                               String routingKey,
+                               ExchangeType exchangeType) {
+
+        super(ackTimeout, responseTimeout, waitForResponses, messageTemplate, forwardNamespace, routingKey);
+        this.exchangeType = exchangeType;
+    }
+
+    public ExchangeType getExchangeType() {
+        return exchangeType;
+    }
+
+    @Override
+    public RequestOptions.Builder asBuilder() {
+        return ((AmqpRequestOptions.Builder) (new Builder().from(this))).withExchangeType(this.exchangeType);
+    }
+
+    public static class Builder extends RequestOptions.Builder {
+
+        private ExchangeType exchangeType;
+
+        public Builder withExchangeType(ExchangeType exchangeType){
+            this.exchangeType = exchangeType;
+            return this;
+        }
+
+        @Override
+        public RequestOptions build() {
+            return new AmqpRequestOptions(ackTimeout, responseTimeout, waitForResponses, messageTemplate,
+                    forwardNamespace, routingKey, exchangeType);
+        }
+    }
+}
diff --git a/amqp/src/main/java/io/github/tcdl/msb/api/AmqpResponderOptions.java b/amqp/src/main/java/io/github/tcdl/msb/api/AmqpResponderOptions.java
new file mode 100644
index 00000000..ced0c117
--- /dev/null
+++ b/amqp/src/main/java/io/github/tcdl/msb/api/AmqpResponderOptions.java
@@ -0,0 +1,55 @@
+package io.github.tcdl.msb.api;
+
+import org.apache.commons.lang3.Validate;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.Set;
+
+
+public class AmqpResponderOptions extends ResponderOptions{
+
+    public static final String MATCH_ALL_BINDING_KEY = "#";
+    private final ExchangeType exchangeType;
+
+    protected AmqpResponderOptions(Set bindingKeys,
+                                   MessageTemplate messageTemplate,
+                                   ExchangeType exchangeType) {
+        super(bindingKeys, messageTemplate);
+        this.exchangeType = exchangeType;
+    }
+
+    public ExchangeType getExchangeType() {
+        return exchangeType;
+    }
+
+    public static class Builder extends ResponderOptions.Builder {
+
+        private ExchangeType exchangeType;
+
+        public Builder withMessageTemplate(MessageTemplate responseMessageTemplate) {
+            this.messageTemplate = responseMessageTemplate;
+            return this;
+        }
+
+        public Builder withBindingKeys(Set bindingKeys) {
+            this.bindingKeys = bindingKeys;
+            return this;
+        }
+
+        public Builder withExchangeType(@Nonnull ExchangeType exchangeType){
+            Validate.notNull(exchangeType);
+            this.exchangeType = exchangeType;
+            return this;
+        }
+
+        @Override
+        public ResponderOptions build() {
+            Set bindingKeys = this.bindingKeys == null || this.bindingKeys.isEmpty()
+                    ? Collections.singleton(MATCH_ALL_BINDING_KEY)
+                    : this.bindingKeys;
+
+            return new AmqpResponderOptions(bindingKeys, messageTemplate, exchangeType);
+        }
+    }
+}
diff --git a/amqp/src/main/java/io/github/tcdl/msb/api/ExchangeType.java b/amqp/src/main/java/io/github/tcdl/msb/api/ExchangeType.java
new file mode 100644
index 00000000..6307de9a
--- /dev/null
+++ b/amqp/src/main/java/io/github/tcdl/msb/api/ExchangeType.java
@@ -0,0 +1,11 @@
+package io.github.tcdl.msb.api;
+
+public enum ExchangeType {
+
+    FANOUT,
+    TOPIC;
+
+    public String value(){
+        return this.name().toLowerCase();
+    }
+}
diff --git a/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java b/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java
index 3bd88fcb..9117c42d 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java
@@ -1,5 +1,6 @@
 package io.github.tcdl.msb.config.amqp;
 
+import io.github.tcdl.msb.api.ExchangeType;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
 import io.github.tcdl.msb.config.ConfigurationUtil;
 
@@ -21,6 +22,7 @@ public class AmqpBrokerConfig {
 
     private Optional groupId;
     private final boolean durable;
+    private ExchangeType defaultExchangeType;
     private final int heartbeatIntervalSec;
     private final long networkRecoveryIntervalMs;
     private final int prefetchCount;
@@ -28,6 +30,7 @@ public class AmqpBrokerConfig {
     public AmqpBrokerConfig(Charset charset, String host, int port,
             Optional username, Optional password, Optional virtualHost, boolean useSSL,
             Optional groupId, boolean durable,
+            ExchangeType defaultExchangeType,
             int heartbeatIntervalSec, long networkRecoveryIntervalMs, int prefetchCount) {
         this.charset = charset;
         this.port = port;
@@ -38,6 +41,7 @@ public AmqpBrokerConfig(Charset charset, String host, int port,
         this.useSSL = useSSL;
         this.groupId = groupId;
         this.durable = durable;
+        this.defaultExchangeType = defaultExchangeType;
         this.heartbeatIntervalSec = heartbeatIntervalSec;
         this.networkRecoveryIntervalMs = networkRecoveryIntervalMs;
         this.prefetchCount = prefetchCount;
@@ -53,6 +57,7 @@ public static class AmqpBrokerConfigBuilder {
         private boolean useSSL;
         private Optional groupId;
         private boolean durable;
+        private ExchangeType defaultExchangeType;
         private int heartbeatIntervalSec;
         private long networkRecoveryIntervalMs;
         private int prefetchCount;
@@ -80,6 +85,7 @@ public AmqpBrokerConfigBuilder withConfig(Config config) {
 
             this.groupId = ConfigurationUtil.getOptionalString(config, "groupId");
             this.durable = ConfigurationUtil.getBoolean(config, "durable");
+            this.defaultExchangeType = ExchangeType.valueOf(ConfigurationUtil.getString(config, "defaultExchangeType").toUpperCase());
             this.heartbeatIntervalSec = ConfigurationUtil.getInt(config, "heartbeatIntervalSec");
             this.networkRecoveryIntervalMs = ConfigurationUtil.getLong(config, "networkRecoveryIntervalMs");
             this.prefetchCount = ConfigurationUtil.getInt(config, "prefetchCount");
@@ -91,7 +97,7 @@ public AmqpBrokerConfigBuilder withConfig(Config config) {
          */
         public AmqpBrokerConfig build() {
             return new AmqpBrokerConfig(charset, host, port, username, password, virtualHost, useSSL,
-                    groupId, durable,
+                    groupId, durable, defaultExchangeType,
                     heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount);
         }
     }
@@ -132,6 +138,10 @@ public boolean isDurable() {
         return durable;
     }
 
+    public ExchangeType getDefaultExchangeType() {
+        return defaultExchangeType;
+    }
+
     public void setGroupId(Optional groupId) {
         this.groupId = groupId;
     }
diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf
index af6ad658..725e2f4e 100644
--- a/amqp/src/main/resources/amqp.conf
+++ b/amqp/src/main/resources/amqp.conf
@@ -16,6 +16,10 @@ config.amqp = {
   #groupId = "msb-java"
   durable = false
 
+  # AMQP exchange type to be used if other is not specified by client code.
+  # Currently supported exchange types are "fanout" and "topic"
+  defaultExchangeType = "fanout"
+
   # Interval of the heartbeats that are used to detect broken connections. Zero for none. See for more details: https://www.rabbitmq.com/heartbeats.html
   heartbeatIntervalSec = 30
   # Interval of connection recovery attempts. See for more details: https://www.rabbitmq.com/api-guide.html#connection-recovery
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
index aa335fd5..473994cc 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
@@ -1,32 +1,29 @@
 package io.github.tcdl.msb.adapters.amqp;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.verify;
-
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
-import io.github.tcdl.msb.adapters.ProducerAdapter;
+import io.github.tcdl.msb.api.ExchangeType;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
-
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.Optional;
-
 import org.junit.Before;
 import org.junit.Test;
-
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-import com.typesafe.config.Config;
-import com.typesafe.config.ConfigFactory;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
 @RunWith(MockitoJUnitRunner.class)
 public class AmqpAdapterFactoryTest {
 
@@ -57,37 +54,33 @@ public class AmqpAdapterFactoryTest {
     AmqpBrokerConfig amqpConfig;
     AmqpAdapterFactory amqpAdapterFactory;
     MsbConfig msbConfigurations;
-    
+
     @Before
     public void setUp() {
 
         msbConfigurations = new MsbConfig(CONFIG);
 
         amqpConfig = new AmqpBrokerConfig(charset, host, port,
-                Optional.of(username), Optional.of(password), Optional.of(virtualHost), useSSL, Optional.of(groupId), durable,
+                Optional.of(username), Optional.of(password), Optional.of(virtualHost), useSSL, Optional.of(groupId), durable, ExchangeType.FANOUT,
                 heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount);
-        
+
         amqpAdapterFactory = new AmqpAdapterFactory() {
-            @Override
-            public ProducerAdapter createProducerAdapter(String topic) {
-                return new AmqpProducerAdapter(topic, amqpConfig, mockConnectionManager);
-            }
 
             @Override
-            public ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
-                return new AmqpConsumerAdapter(topic, amqpConfig, mockConnectionManager, isResponseTopic);
+            public AmqpConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
+                return new AmqpConsumerAdapter(topic, ExchangeType.FANOUT, Collections.emptySet(), amqpConfig, mockConnectionManager, isResponseTopic);
             }
 
             @Override
             protected ConnectionFactory createConnectionFactory() {
                 return mockConnectionFactory;
             }
-            
+
             @Override
             protected AmqpBrokerConfig createAmqpBrokerConfig(MsbConfig msbConfig) {
                 return amqpConfig;
             }
-            
+
             @Override
             protected AmqpConnectionManager createConnectionManager(Connection connection) {
                 return mockConnectionManager;
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
index 013510b8..58e17e01 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
@@ -5,6 +5,8 @@
 import com.rabbitmq.client.Connection;
 import com.rabbitmq.client.Consumer;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.api.ExchangeType;
+import io.github.tcdl.msb.api.ResponderOptions;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import org.apache.commons.collections.CollectionUtils;
@@ -36,7 +38,7 @@ public void setUp() throws Exception {
         Connection mockConnection = mock(Connection.class);
         mockChannel = mock(Channel.class);
         mockAmqpConnectionManager = mock(AmqpConnectionManager.class);
-        
+
         when(mockAmqpConnectionManager.obtainConnection()).thenReturn(mockConnection);
         when(mockConnection.createChannel()).thenReturn(mockChannel);
     }
@@ -57,9 +59,9 @@ public void testFanoutExchangeCreated() throws Exception {
     public void testTopicExchangeCreated() throws Exception {
         String topicName = "myTopic";
         String groupId = "groupId";
-        String routingKey = "routing-key";
+        String bindingKey = "binding-key";
 
-        new AmqpConsumerAdapter(topicName, Collections.singleton(routingKey), brokerConfig(groupId, true), mockAmqpConnectionManager);
+        new AmqpConsumerAdapter(topicName, ExchangeType.TOPIC, Collections.singleton(bindingKey), brokerConfig(groupId, true), mockAmqpConnectionManager, true);
         verify(mockChannel).exchangeDeclare(topicName, "topic", false, true, null);
 
     }
@@ -76,21 +78,22 @@ public void testSubscribeMultipleRoutingKeysMultipleBindings() throws Exception
         String topicName = "myTopic";
         String groupId = "groupId";
 
-        Set routingKeys = Sets.newHashSet("routing-key-1", "routing-key-2");
+        Set bindingKeys = Sets.newHashSet("routing-key-1", "routing-key-2");
 
-        AmqpConsumerAdapter amqpConsumerAdapter = new AmqpConsumerAdapter(topicName, routingKeys, brokerConfig(groupId, true), mockAmqpConnectionManager);
-        amqpConsumerAdapter.subscribe((jsonMessage, acknowledgementHandler) -> {});
+        AmqpConsumerAdapter amqpConsumerAdapter = new AmqpConsumerAdapter(topicName, ExchangeType.TOPIC, bindingKeys, brokerConfig(groupId, true), mockAmqpConnectionManager, false);
+        amqpConsumerAdapter.subscribe((jsonMessage, acknowledgementHandler) -> {
+        });
 
         ArgumentCaptor routingKeysCaptor = ArgumentCaptor.forClass(String.class);
         verify(mockChannel, times(2)).queueBind(eq("myTopic.groupId.d"), eq(topicName), routingKeysCaptor.capture());
 
-        assertTrue(CollectionUtils.isEqualCollection(routingKeys, routingKeysCaptor.getAllValues()));
+        assertTrue(CollectionUtils.isEqualCollection(bindingKeys, routingKeysCaptor.getAllValues()));
     }
 
     @Test
     public void testSubscribeTransientQueueCreated() throws IOException {
         AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", false);
-        
+
         adapter.subscribe((jsonMessage, ackHandler) -> {
         });
 
@@ -144,7 +147,8 @@ public void testSubscribeException() throws IOException {
         AmqpConsumerAdapter adapter = createAdapterWithDurableConf("myTopic", "myGroupId", false);
         when(mockChannel.basicConsume(anyString(), anyBoolean(), any(AmqpMessageConsumer.class)))
                 .thenThrow(IOException.class);
-        adapter.subscribe((jsonMessage, ackHandler) -> {});
+        adapter.subscribe((jsonMessage, ackHandler) -> {
+        });
     }
 
     @Test
@@ -182,7 +186,8 @@ public void testUnsubscribeException() throws IOException {
         String consumerTag = "my consumer tag";
         when(mockChannel.basicConsume(anyString(), anyBoolean(), any(Consumer.class)))
                 .thenThrow(IOException.class);
-        adapter.subscribe((jsonMessage, ackHandler) -> {});
+        adapter.subscribe((jsonMessage, ackHandler) -> {
+        });
         adapter.unsubscribe();
     }
 
@@ -225,22 +230,22 @@ public void testIsDurableTrueIfNotResponseTopicAndDurableConfig() throws IOExcep
     private AmqpConsumerAdapter createAdapterWithNonDurableConf(String topic, String groupId, boolean isResponseTopic) {
         boolean isDurableConf = false;
         AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
-                false, Optional.of(groupId), isDurableConf, 1, 5000, 1);
-        return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
+                false, Optional.of(groupId), isDurableConf, ExchangeType.FANOUT, 1, 5000, 1);
+        return new AmqpConsumerAdapter(topic, ExchangeType.FANOUT, ResponderOptions.DEFAULTS.getBindingKeys(), nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
     }
 
     private AmqpConsumerAdapter createAdapterWithDurableConf(String topic, String groupId, boolean isResponseTopic) {
         boolean isDurableConf = true;
         AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
-                false, Optional.of(groupId), isDurableConf, 1, 5000, 1);
-        return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
+                false, Optional.of(groupId), isDurableConf, ExchangeType.FANOUT, 1, 5000, 1);
+        return new AmqpConsumerAdapter(topic, ExchangeType.FANOUT, ResponderOptions.DEFAULTS.getBindingKeys(), nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
     }
 
     private AmqpBrokerConfig brokerConfig(String groupId, boolean durable) {
         return new AmqpBrokerConfig(
                 Charset.forName("UTF-8"),
                 "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
-                false, Optional.of(groupId), durable, 1, 5000, 1
+                false, Optional.of(groupId), durable, ExchangeType.FANOUT, 1, 5000, 1
         );
     }
 }
\ No newline at end of file
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java
index 4bdaa162..9bbb71fb 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java
@@ -4,6 +4,7 @@
 import com.rabbitmq.client.Channel;
 import com.rabbitmq.client.Connection;
 import com.rabbitmq.client.MessageProperties;
+import io.github.tcdl.msb.api.ExchangeType;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import org.apache.commons.lang3.StringUtils;
@@ -40,25 +41,27 @@ public void setUp() throws IOException {
     }
 
     @Test
-    public void testExchangeCreated() throws IOException {
+    public void testExchangeWithCorrectTypeCreated() throws IOException {
         String topicName = "myTopic";
 
-        new AmqpProducerAdapter(topicName, mockAmqpBrokerConfig, mockAmqpConnectionManager);
-
+        new AmqpProducerAdapter(topicName, ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager);
         verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null);
+
+        new AmqpProducerAdapter(topicName, ExchangeType.TOPIC, mockAmqpBrokerConfig, mockAmqpConnectionManager);
+        verify(mockChannel).exchangeDeclare(topicName, "topic", false, true, null);
     }
 
     @Test(expected = RuntimeException.class)
     public void testInitializationError() throws IOException {
         when(mockChannel.exchangeDeclare(anyString(), anyString(), anyBoolean(), anyBoolean(), any())).thenThrow(new IOException());
-        new AmqpProducerAdapter("myTopic", mockAmqpBrokerConfig, mockAmqpConnectionManager);
+        new AmqpProducerAdapter("myTopic", ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager);
     }
 
     @Test
     public void testPublish() throws ChannelException, IOException {
         String topicName = "myTopic";
         String message = "message";
-        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, mockAmqpBrokerConfig, mockAmqpConnectionManager);
+        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager);
 
         producerAdapter.publish(message);
 
@@ -70,7 +73,7 @@ public void testPublishWithRoutingKey() throws Exception{
         String topicName = "myTopic";
         String message = "message";
         String routingKey = "routingKey";
-        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, mockAmqpBrokerConfig, mockAmqpConnectionManager);
+        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, ExchangeType.TOPIC, mockAmqpBrokerConfig, mockAmqpConnectionManager);
 
         producerAdapter.publish(message, routingKey);
         verify(mockChannel).basicPublish(topicName, routingKey, MessageProperties.PERSISTENT_BASIC, message.getBytes());
@@ -82,7 +85,7 @@ public void testProperCharsetUsed() throws IOException {
 
         String message = "ö";
         byte[] expectedEncodedMessage = new byte[] { 0, 0, 0, -10 }; // In UTF-32 ö is mapped to 000000f6
-        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter("myTopic", mockAmqpBrokerConfig, mockAmqpConnectionManager);
+        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter("myTopic", ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager);
 
         producerAdapter.publish(message);
 
diff --git a/amqp/src/test/java/io/github/tcdl/msb/api/AmqpResponderOptionsBuilderTest.java b/amqp/src/test/java/io/github/tcdl/msb/api/AmqpResponderOptionsBuilderTest.java
new file mode 100644
index 00000000..8f902d88
--- /dev/null
+++ b/amqp/src/test/java/io/github/tcdl/msb/api/AmqpResponderOptionsBuilderTest.java
@@ -0,0 +1,16 @@
+package io.github.tcdl.msb.api;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+public class AmqpResponderOptionsBuilderTest {
+
+    @Test
+    public void build_shouldSetHashBindingKeyByDefault() throws Exception {
+        ResponderOptions responderOptions = new AmqpResponderOptions.Builder().build();
+        assertEquals(Collections.singleton("#"), responderOptions.getBindingKeys());
+    }
+}
\ No newline at end of file
diff --git a/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java b/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java
index 7b60e634..a295ba9b 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java
@@ -24,6 +24,7 @@ public class AmqpBrokerConfigTest {
     final boolean useSSL = false;
     final String groupId = "msb-java";
     final boolean durable = false;
+    final String exchangeType = "fanout";
     final int heartbeatIntervalSec = 1;
     final long networkRecoveryIntervalMs = 5000;
     final int prefetchCount = 1;
@@ -40,6 +41,7 @@ public void testBuildAmqpBrokerConfig() {
                 + " useSSL = \"" + useSSL + "\"\n"
                 + " groupId = \"" + groupId + "\"\n"
                 + " durable = " + durable + "\n"
+                + " defaultExchangeType = " + exchangeType + "\n"
                 + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n"
                 + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n"
                 + " prefetchCount = " + prefetchCount + "\n"
@@ -75,6 +77,7 @@ public void testOptionalConfigurationOptions() {
                 + " port = \"" + port + "\"\n"
                 + " useSSL = \"" + useSSL + "\"\n"
                 + " durable = " + durable + "\n"
+                + " defaultExchangeType = " + exchangeType + "\n"
                 + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n"
                 + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n"
                 + " prefetchCount = " + prefetchCount + "\n"
@@ -222,6 +225,7 @@ public void testHeartbeatIntervalOption() {
                 + " useSSL = \"" + useSSL + "\"\n"
                 + " groupId = \"" + groupId + "\"\n"
                 + " durable = " + durable + "\n"
+                + " defaultExchangeType = " + exchangeType + "\n"
                 + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n"
                 + " prefetchCount = " + prefetchCount + "\n"
                 + "}";
@@ -241,6 +245,7 @@ public void testNetworkRecoveryIntervalOption() {
                 + " useSSL = \"" + useSSL + "\"\n"
                 + " groupId = \"" + groupId + "\"\n"
                 + " durable = " + durable + "\n"
+                + " defaultExchangeType = " + exchangeType + "\n"
                 + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n"
                 + " prefetchCount = " + prefetchCount + "\n"
                 + "}";
@@ -260,6 +265,7 @@ public void testPrefetchCountOption() {
                 + " useSSL = \"" + useSSL + "\"\n"
                 + " groupId = \"" + groupId + "\"\n"
                 + " durable = " + durable + "\n"
+                + " defaultExchangeType = " + exchangeType + "\n"
                 + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n"
                 + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n"
                 + "}";
diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
index 4c03b282..32b227c2 100644
--- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
@@ -2,15 +2,14 @@
 
 import java.time.Clock;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.github.tcdl.msb.adapters.*;
 import io.github.tcdl.msb.api.Callback;
-import io.github.tcdl.msb.api.MessageDestination;
+import io.github.tcdl.msb.api.RequestOptions;
+import io.github.tcdl.msb.api.ResponderOptions;
 import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException;
-import io.github.tcdl.msb.api.exception.MsbException;
 import io.github.tcdl.msb.collector.CollectorManager;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.api.message.Message;
@@ -40,11 +39,8 @@ public class ChannelManager {
     private final MessageHandlerInvoker messageHandlerInvoker;
     private ChannelMonitorAgent channelMonitorAgent;
 
-    private final Map broadcastProducersByTopic;
-    private final Map multicastProducersByTopic;
-
-    private final Map broadcastConsumersByTopic;
-    private final Map multicastConsumersByTopic;
+    private final Map producersByTopic;
+    private final Map consumersByTopic;
 
     public ChannelManager(MsbConfig msbConfig, Clock clock, JsonValidator validator, ObjectMapper messageMapper, AdapterFactory adapterFactory, MessageHandlerInvoker messageHandlerInvoker) {
         this.msbConfig = msbConfig;
@@ -53,37 +49,20 @@ public ChannelManager(MsbConfig msbConfig, Clock clock, JsonValidator validator,
         this.messageMapper = messageMapper;
         this.adapterFactory = adapterFactory;
         this.messageHandlerInvoker = messageHandlerInvoker;
-        this.broadcastProducersByTopic = new ConcurrentHashMap<>();
-        this.multicastProducersByTopic = new ConcurrentHashMap<>();
-        this.broadcastConsumersByTopic = new ConcurrentHashMap<>();
-        this.multicastConsumersByTopic = new ConcurrentHashMap<>();
+
+        this.producersByTopic = new ConcurrentHashMap<>();
+        this.consumersByTopic = new ConcurrentHashMap<>();
 
         channelMonitorAgent = new NoopChannelMonitorAgent();
     }
 
-    public Producer findOrCreateProducer(final String topic) {
-        Validate.notNull(topic, "field 'topic' is null");
-        if(multicastProducersByTopic.containsKey(topic)){
-            throw new MsbException("Producer for " + topic + " already exists for multicast mode.");
-        }
-        Producer producer = broadcastProducersByTopic.computeIfAbsent(topic, key -> {
-            Producer newProducer = createProducer(key);
-            channelMonitorAgent.producerTopicCreated(key);
-            return newProducer;
-        });
+    public Producer findOrCreateProducer(String topic, RequestOptions requestOptions) {
+        Validate.notEmpty(topic, "Topic is mandatory");
+        Validate.notNull(requestOptions, "RequestOptions are mandatory");
 
-        return producer;
-    }
-
-    public Producer findOrCreateProducer(MessageDestination destination) {
-        Validate.notNull(destination, "destination is mandatory");
-        String topic = destination.getTopic();
-        if(broadcastProducersByTopic.containsKey(topic)){
-            throw new MsbException("Producer for " + topic + " already exists for broadcast mode.");
-        }
-        Producer producer = multicastProducersByTopic.computeIfAbsent(topic, namespace -> {
-            Producer newProducer = createProducer(destination);
-            channelMonitorAgent.producerTopicCreated(namespace);
+        Producer producer = producersByTopic.computeIfAbsent(topic, key -> {
+            Producer newProducer = createProducer(key, requestOptions);
+            channelMonitorAgent.producerTopicCreated(key);
             return newProducer;
         });
 
@@ -97,67 +76,33 @@ public Producer findOrCreateProducer(MessageDestination destination) {
      * @param messageHandler handler for processing messages
      * @throws ConsumerSubscriptionException if subscriber for topic already exist
      */
-    public synchronized void subscribe(String topic, Set routingKeys, MessageHandler messageHandler) {
-        Validate.notBlank(topic, "field 'topic' is empty");
-        Validate.notEmpty(routingKeys, "At least one routing key is required");
+    public synchronized void subscribe(String topic, ResponderOptions responderOptions, MessageHandler messageHandler) {
+        Validate.notBlank(topic, "Topic should not be empty");
+        Validate.notNull(responderOptions, "ResponderOptions are mandatory");
 
-        if(broadcastConsumersByTopic.get(topic) != null){
-            throw new ConsumerSubscriptionException("Consumer for " + topic + " already exists for broadcast mode.");
-        }
-
-        if (multicastConsumersByTopic.get(topic) != null) {
-            throw new ConsumerSubscriptionException("Subscriber for this topic " + topic + " already exist");
-        } else {
-            Consumer newConsumer = createConsumer(topic, routingKeys, new SimpleMessageHandlerResolverImpl(messageHandler, RESPONDER_LOGGING_NAME));
-            channelMonitorAgent.consumerTopicCreated(topic);
-            multicastConsumersByTopic.put(topic, newConsumer);
-        }
+        MessageHandlerResolver messageHandlerResolver = new SimpleMessageHandlerResolverImpl(messageHandler, RESPONDER_LOGGING_NAME);
+        subscribe(topic, false, responderOptions, messageHandlerResolver);
     }
 
     /**
-     * * Start consuming messages on specified topic with handler.
+     * {@link ChannelManager#subscribe(String, ResponderOptions, MessageHandler)} with default responderOptions
      */
     public void subscribe(String topic, MessageHandler messageHandler) {
-        Validate.notNull(topic, "field 'topic' is null");
-        Validate.notNull(messageHandler, "field 'messageHandler' is null");
-
-        if(multicastConsumersByTopic.get(topic) != null){
-            throw new ConsumerSubscriptionException("Consumer for " + topic + " already exists for multicast mode.");
-        }
-
-        if (broadcastConsumersByTopic.get(topic) != null) {
-            throw new ConsumerSubscriptionException("Subscriber for this topic " + topic + " already exist");
-        } else {
-            Consumer newConsumer = createConsumer(topic, false, new SimpleMessageHandlerResolverImpl(messageHandler, RESPONDER_LOGGING_NAME));
-            channelMonitorAgent.consumerTopicCreated(topic);
-            broadcastConsumersByTopic.put(topic, newConsumer);
-        }
+        subscribe(topic, ResponderOptions.DEFAULTS, messageHandler);
     }
 
     /**
-     * Start consuming response messages on specified topic and pass processing to CollectorManager.
-     * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages.
+     * Start consuming response messages.
      *
-     * @param topic
+     * @param topic response topic
      * @param collectorManager resolver of {@link MessageHandler}  for processing messages
      * @throws ConsumerSubscriptionException if subscriber for topic already exist
      */
-    public synchronized boolean subscribeForResponses(String topic, CollectorManager collectorManager) {
-        Validate.notNull(topic, "field 'topic' is null");
+    public synchronized void subscribeForResponses(String topic, CollectorManager collectorManager) {
+        Validate.notBlank(topic, "Topic should not be empty");
         Validate.notNull(collectorManager, "field 'collectorManager' is null");
 
-        if(multicastConsumersByTopic.get(topic) != null){
-            throw new ConsumerSubscriptionException("Consumer for " + topic + " already exists for multicast mode.");
-        }
-
-        if (broadcastConsumersByTopic.get(topic) != null) {
-            throw new ConsumerSubscriptionException("Subscriber for this topic: " + topic + " already exist");
-        } else {
-            Consumer newConsumer = createConsumer(topic, true, collectorManager);
-            broadcastConsumersByTopic.put(topic, newConsumer);
-            channelMonitorAgent.consumerTopicCreated(topic);
-            return false;
-        }
+        subscribe(topic, true, ResponderOptions.DEFAULTS, collectorManager);
     }
 
     /**
@@ -165,12 +110,19 @@ public synchronized boolean subscribeForResponses(String topic, CollectorManager
      * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages.
      */
     public synchronized void unsubscribe(String topic) {
-        if(broadcastConsumersByTopic.get(topic) != null){
-            stopConsumer(topic, broadcastConsumersByTopic.remove(topic));
+        if (consumersByTopic.get(topic) != null) {
+            stopConsumer(topic, consumersByTopic.remove(topic));
         }
+    }
 
-        if(multicastConsumersByTopic.get(topic) != null){
-            stopConsumer(topic, multicastConsumersByTopic.remove(topic));
+    private void subscribe(String topic, boolean isResponseTopic, ResponderOptions responderOptions, MessageHandlerResolver messageHandlerResolver) {
+        if (consumersByTopic.get(topic) != null) {
+            throw new ConsumerSubscriptionException("Subscriber for topic " + topic + " already exist");
+        } else {
+            Consumer newConsumer = createConsumer(topic, isResponseTopic, responderOptions, messageHandlerResolver);
+            newConsumer.subscribe();
+            channelMonitorAgent.consumerTopicCreated(topic);
+            consumersByTopic.put(topic, newConsumer);
         }
     }
 
@@ -181,35 +133,16 @@ private void stopConsumer(String topic, Consumer consumer) {
         }
     }
 
-    private Producer createProducer(String topic) {
+    private Producer createProducer(String topic, RequestOptions requestOptions) {
         Utils.validateTopic(topic);
-
-        ProducerAdapter adapter = getAdapterFactory().createProducerAdapter(topic);
-        Callback handler = message -> channelMonitorAgent.producerMessageSent(topic);
-        return new Producer(adapter, topic, handler, messageMapper);
+        ProducerAdapter adapter = this.adapterFactory.createProducerAdapter(topic, requestOptions);
+        Callback monitorAgentCallback = message -> channelMonitorAgent.producerMessageSent(topic);
+        return new Producer(adapter, topic, monitorAgentCallback, messageMapper);
     }
 
-    private Producer createProducer(MessageDestination destination) {
-        String topic = destination.getTopic();
-        Utils.validateTopic(topic);
-
-        ProducerAdapter adapter = getAdapterFactory().createProducerAdapter(destination);
-        Callback handler = message -> channelMonitorAgent.producerMessageSent(topic);
-        return new Producer(adapter, topic, handler, messageMapper);
-    }
-
-    private Consumer createConsumer(String topic, boolean isResponseTopic, MessageHandlerResolver messageHandlerResolver) {
-        ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic, isResponseTopic);
-        return createConsumer(topic, adapter, messageHandlerResolver);
-    }
-
-    private Consumer createConsumer(String topic, Set routingKeys, MessageHandlerResolver messageHandlerResolver) {
-        ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic, routingKeys);
-        return createConsumer(topic, adapter, messageHandlerResolver);
-    }
-
-    private Consumer createConsumer(String topic, ConsumerAdapter adapter, MessageHandlerResolver messageHandlerResolver){
+    private Consumer createConsumer(String topic, boolean isResponseTopic, ResponderOptions responderOptions, MessageHandlerResolver messageHandlerResolver) {
         Utils.validateTopic(topic);
+        ConsumerAdapter adapter = this.adapterFactory.createConsumerAdapter(topic, responderOptions, isResponseTopic);
         return new Consumer(adapter, messageHandlerInvoker, topic, messageHandlerResolver, msbConfig, clock, channelMonitorAgent, validator, messageMapper);
     }
 
@@ -220,11 +153,6 @@ public void shutdown() {
         LOG.info("Shutdown complete");
     }
 
-    private AdapterFactory getAdapterFactory() {
-        return this.adapterFactory;
-    }
-
-
     public void setChannelMonitorAgent(ChannelMonitorAgent channelMonitorAgent) {
         this.channelMonitorAgent = channelMonitorAgent;
     }
diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java
index 92981535..ecd062c8 100644
--- a/core/src/main/java/io/github/tcdl/msb/Consumer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java
@@ -78,12 +78,17 @@ public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvoker messageHandler
         this.validator = validator;
         this.messageMapper = messageMapper;
 
-        this.rawAdapter.subscribe(this::handleRawMessage);
-
         this.loggingTag = String.format("[Consumer for: '%s' on topic: '%s']", messageHandlerResolver.getLoggingName(), topic);
         this.isSplitTagsForMdcLogging = !StringUtils.isEmpty(msbConfig.getMdcLoggingSplitTagsBy());
     }
 
+    /**
+     * Start consuming messages
+     */
+    public void subscribe() {
+        this.rawAdapter.subscribe(this::handleRawMessage);
+    }
+
     /**
      * Stop consuming messages for specified topic.
      */
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
index 8219ba7f..210ef477 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
@@ -1,11 +1,10 @@
 package io.github.tcdl.msb.adapters;
 
-import io.github.tcdl.msb.api.MessageDestination;
-import io.github.tcdl.msb.config.MsbConfig;
+import io.github.tcdl.msb.api.RequestOptions;
+import io.github.tcdl.msb.api.ResponderOptions;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
-
-import java.util.Set;
+import io.github.tcdl.msb.config.MsbConfig;
 
 /**
  * MSBAdapterFactory interface represents a common way for creation a particular AdapterFactory
@@ -25,15 +24,14 @@ public interface AdapterFactory {
      * @param topic topic name
      * @return Producer Adapter associated with a topic
      * @throws ChannelException if some problems during creation were occurred
+     * @deprecated use {@link AdapterFactory#createProducerAdapter(String, RequestOptions)}
      */
-    ProducerAdapter createProducerAdapter(String topic);
+    @Deprecated
+    default ProducerAdapter createProducerAdapter(String topic) {
+        return createProducerAdapter(topic, RequestOptions.DEFAULTS);
+    }
 
-    /**
-     * @param destination message destination
-     * @return Producer Adapter associated with a topic
-     * @throws ChannelException if some problems during creation were occurred
-     */
-    ProducerAdapter createProducerAdapter(MessageDestination destination);
+    ProducerAdapter createProducerAdapter(String topic, RequestOptions requestOptions);
 
     /**
      * @param topic topic name
@@ -44,14 +42,11 @@ public interface AdapterFactory {
     ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic);
 
     /**
-     * Creates ConsumerAdapter associated with a topic. Implementation must guarantee
-     * that bindings by provided routing keys are created. However, it does not guarantee
-     * that other bindings for the same queue do or do not exist.
+     * Creates ConsumerAdapter associated with a topic.
      * @param topic topic name
-     * @param routingKeys routing keys to be used for binding
      * @throws ChannelException if a problems has occurred during creation
      */
-    ConsumerAdapter createConsumerAdapter(String topic, Set routingKeys);
+    ConsumerAdapter createConsumerAdapter(String topic, ResponderOptions responderOptions, boolean isResponseTopic);
 
     /**
      * @return true if custom MSB threading model should be used.
diff --git a/core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java b/core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java
deleted file mode 100644
index 606c383e..00000000
--- a/core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package io.github.tcdl.msb.api;
-
-import org.apache.commons.lang3.Validate;
-
-public class MessageDestination {
-
-    private final String topic;
-    private final String routingKey;
-
-    public MessageDestination(String topic, String routingKey) {
-        Validate.notNull(topic, "topic is mandatory");
-        Validate.notNull(routingKey, "routingKey is mandatory");
-        this.topic = topic;
-        this.routingKey = routingKey;
-    }
-
-    public String getTopic() {
-        return topic;
-    }
-
-    public String getRoutingKey() {
-        return routingKey;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        MessageDestination that = (MessageDestination) o;
-
-        if (!topic.equals(that.topic)) return false;
-        return routingKey.equals(that.routingKey);
-
-    }
-
-    @Override
-    public int hashCode() {
-        int result = topic.hashCode();
-        result = 31 * result + routingKey.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "MessageDestination{" +
-                "topic='" + topic + '\'' +
-                ", routingKey='" + routingKey + '\'' +
-                '}';
-    }
-}
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
index d5a62dd9..0952c3b5 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
@@ -9,7 +9,7 @@
 import java.util.Set;
 
 /**
- * Provides methods for creation client-facing API classes.
+ * Provides methods for creation client-facing API objects.
  */
 public interface ObjectFactory {
 
@@ -37,49 +37,54 @@ public Type getType() {
     }
 
     /**
-     * @param namespace             topic name to send a request to
-     * @param requestOptions        options to configure a requester
-     * @param payloadTypeReference  expected payload type of response messages
-     * @return new instance of a {@link Requester} with original message
+     * @param namespace            topic name to send a request to
+     * @param requestOptions       options to configure a requester
+     * @param payloadTypeReference expected payload type of response messages
+     * @return new instance of a {@link Requester}
      */
      Requester createRequester(String namespace, RequestOptions requestOptions, TypeReference payloadTypeReference);
 
     /**
      * Same as
-     * {@link ObjectFactory#createRequesterForSingleResponse(java.lang.String, java.lang.Class, io.github.tcdl.msb.api.RequestOptions)}
+     * {@link #createRequesterForSingleResponse(String, Class, RequestOptions)}
      * with default request options
      */
-     Requester createRequesterForSingleResponse(String namespace, Class payloadClass);
+    default  Requester createRequesterForSingleResponse(String namespace, Class payloadClass) {
+        return createRequesterForSingleResponse(namespace, payloadClass, RequestOptions.DEFAULTS);
+    }
 
     /**
-     * Creates requester for single response with default response and acknowledgment timeouts
+     * Creates requester for single response
      *
      * @param namespace          topic name to send a request to
      * @param payloadClass       expected payload class of response messages
      * @param baseRequestOptions request options to be used as a source of response timeout and {@link MessageTemplate}.
      *                           Response time however will be 1 even if {@code baseRequestOptions} define other value.
-     * @return new instance of a {@link Requester} with original message
+     * @return new instance of a {@link Requester}
      */
      Requester createRequesterForSingleResponse(String namespace, Class payloadClass, RequestOptions baseRequestOptions);
 
     /**
      * Convenience method that specifies incoming payload type as {@link JsonNode}
      *
-     * See {@link #createRequester(String, RequestOptions, TypeReference)}
+     * @deprecated use {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)}
      */
+    @Deprecated
     default ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
-            ResponderServer.RequestHandler requestHandler) {
-        return createResponderServer(namespace, messageTemplate, requestHandler, JsonNode.class);
+                                                  ResponderServer.RequestHandler requestHandler) {
+        return createResponderServer(namespace, new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(), requestHandler, JsonNode.class);
     }
 
     /**
      * Convenience method that allows to specify incoming payload type via {@link Class}
      *
-     * See {@link #createRequester(String, RequestOptions, TypeReference)}
+     * @deprecated use {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)}
      */
+    @Deprecated
     default  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
-            ResponderServer.RequestHandler requestHandler, Class payloadClass) {
-        return createResponderServer(namespace, messageTemplate, requestHandler, new TypeReference() {
+                                                      ResponderServer.RequestHandler requestHandler, Class payloadClass) {
+        ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build();
+        return createResponderServer(namespace, responderOptions, requestHandler, new TypeReference() {
             @Override
             public Type getType() {
                 return payloadClass;
@@ -88,130 +93,112 @@ public Type getType() {
     }
 
     /**
-     * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)}
-     */
-    default ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
-                                              ResponderServer.RequestHandler requestHandler){
-        return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, JsonNode.class);
-    }
-
-    /**
-     * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)}
+     * @param namespace            topic on a bus for listening on incoming requests
+     * @param responderOptions     {@link ResponderOptions} to be used
+     * @param requestHandler       handler for processing the request
+     * @param errorHandler         handler for errors to be called after default
+     * @param payloadTypeReference expected payload type of incoming messages
+     * @return new instance of a {@link ResponderServer} that unmarshals payload into specified payload type
      */
-    default  ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
-                                              ResponderServer.RequestHandler requestHandler, Class payloadClass) {
-        return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, null, payloadClass);
-    }
+     ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions,
+                                              ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference);
 
     /**
-     * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)}
+     * Convenience method that specifies incoming payload type as {@link JsonNode}
+     * 

+ * See {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)} */ - default ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) { - return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, errorHandler, new TypeReference() { + default ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, + ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler) { + return createResponderServer(namespace, responderOptions, requestHandler, errorHandler, new TypeReference() { @Override public Type getType() { - return payloadClass; + return JsonNode.class; } }); } /** - * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)} + * See {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)} */ - default ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate, + default ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) { - return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, null, payloadTypeReference); + return createResponderServer(namespace, responderOptions, requestHandler, null, payloadTypeReference); } /** - * @param namespace topic on a bus for listening on incoming requests - * @param routingKeys routing keys - * @param messageTemplate template of response message - * @param requestHandler handler for processing the request - * @param errorHandler handler for errors to be called after default - * @param payloadTypeReference expected payload type of incoming messages - * @return new instance of a {@link ResponderServer} that unmarshals payload into specified payload type + * See {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)} */ - ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference); + default ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, + ResponderServer.RequestHandler requestHandler, Class payloadClass) { + return createResponderServer(namespace, responderOptions, requestHandler, new TypeReference() { + @Override + public Type getType() { + return payloadClass; + } + }); + } - default ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) { - return createResponderServer(namespace, messageTemplate, requestHandler, errorHandler, new TypeReference() { + default ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, + ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) { + return createResponderServer(namespace, responderOptions, requestHandler, errorHandler, new TypeReference() { @Override public Type getType() { return payloadClass; + } }); } /** - * Same as - * {@link ObjectFactory#createRequesterForFireAndForget(java.lang.String, io.github.tcdl.msb.api.MessageTemplate)} - * with default messageTemplate + * @deprecated use {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)} */ - Requester createRequesterForFireAndForget(String namespace); + @Deprecated + default ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, + ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + return createResponderServer(namespace, responderOptions, requestHandler, errorHandler, payloadTypeReference); + } /** - * Creates requester that doesn't wait for any responses or acknowledgments - * - * @param namespace topic name to send a request to - * @param messageTemplate {@link MessageTemplate} to be used - * @return new instance of a {@link Requester} with original message + * @deprecated use {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)} */ - Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate); + @Deprecated + default ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, + ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) { + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + return createResponderServer(namespace, responderOptions, requestHandler, errorHandler, new TypeReference() { + @Override + public Type getType() { + return payloadClass; + } + }); + } /** - * Creates requester that doesn't wait for any responses or acknowledgments - * - * @param namespace topic to send a request to - * @param forwardTo topic to be used for forwarding - * @param messageTemplate {@link MessageTemplate} to be used - * @return new instance of a {@link Requester} with original message + * See {@link #createRequesterForFireAndForget(java.lang.String, io.github.tcdl.msb.api.RequestOptions)} */ - Requester createRequesterForFireAndForget(String namespace, String forwardTo, MessageTemplate messageTemplate); + default Requester createRequesterForFireAndForget(String namespace){ + return createRequesterForFireAndForget(namespace, RequestOptions.DEFAULTS); + } /** * Creates requester that doesn't wait for any responses or acknowledgments * - * @param messageDestination {@link MessageDestination} to be used - * @param messageTemplate {@link MessageTemplate} to be used * @return new instance of a {@link Requester} with original message */ - Requester createRequesterForFireAndForget(MessageDestination messageDestination, MessageTemplate messageTemplate); + Requester createRequesterForFireAndForget(String namespace, RequestOptions requestOptions); /** - * Creates requester that doesn't wait for any responses or acknowledgments. - * - * @param namespace topic name to send a request to (where consumer with forwarding capabilities is expected) - * @param forwardTo {@link MessageDestination} to be used for forwarding - * @param messageTemplate {@link MessageTemplate} to be used - * @return new instance of a {@link Requester} with original message + * @deprecated use {@link ObjectFactory#createResponderServer(java.lang.String, io.github.tcdl.msb.api.ResponderOptions, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)} */ - Requester createRequesterForFireAndForget(String namespace, MessageDestination forwardTo, MessageTemplate messageTemplate); - - /** - * @param namespace topic on a bus for listening on incoming requests - * @param messageTemplate template used for creating response messages - * @param requestHandler handler for processing the request - * @param payloadTypeReference expected payload type of incoming messages - * @return new instance of a {@link ResponderServer} that unmarshals payload into specified payload type - */ - ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference); - - /** - * @param namespace topic on a bus for listening on incoming requests - * @param messageTemplate template used for creating response messages - * @param requestHandler handler for processing the request - * @param errorHandler handler for errors to be called after default - * @param payloadTypeReference expected payload type of incoming messages - * @return new instance of a {@link ResponderServer} that unmarshals payload into specified payload type - */ - ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference); + @Deprecated + default ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, + ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) { + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + return createResponderServer(namespace, responderOptions, requestHandler, payloadTypeReference); + } /** * @return instance of converter to convert any objects diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java index 347e7b8a..57342746 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java +++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java @@ -9,6 +9,7 @@ public class RequestOptions { public static final int WAIT_FOR_RESPONSES_UNTIL_TIMEOUT = -1; + public static final RequestOptions DEFAULTS = new Builder().build(); /** * Max time (in milliseconds) to wait for acknowledgements. */ @@ -38,7 +39,7 @@ public class RequestOptions { private final String routingKey; - private RequestOptions(Integer ackTimeout, Integer responseTimeout, Integer waitForResponses, MessageTemplate messageTemplate, String forwardNamespace, String routingKey) { + protected RequestOptions(Integer ackTimeout, Integer responseTimeout, Integer waitForResponses, MessageTemplate messageTemplate, String forwardNamespace, String routingKey) { this.ackTimeout = ackTimeout; this.responseTimeout = responseTimeout; this.waitForResponses = waitForResponses; @@ -57,7 +58,7 @@ public Integer getResponseTimeout() { public int getWaitForResponses() { if (waitForResponses == null || waitForResponses == -1) { - // use for Infinity number or expected responses + // use for infinite number or expected responses return WAIT_FOR_RESPONSES_UNTIL_TIMEOUT; } else { return waitForResponses; @@ -76,6 +77,9 @@ public String getRoutingKey() { return routingKey; } + public Builder asBuilder() { + return new RequestOptions.Builder().from(this); + } @Override public String toString() { @@ -89,12 +93,12 @@ public String toString() { public static class Builder { - private String routingKey; - private Integer ackTimeout; - private Integer responseTimeout; - private Integer waitForResponses; - private MessageTemplate messageTemplate; - private String forwardNamespace; + protected String routingKey; + protected Integer ackTimeout; + protected Integer responseTimeout; + protected Integer waitForResponses; + protected MessageTemplate messageTemplate; + protected String forwardNamespace; public Builder withRoutingKey(String routingKey) { this.routingKey = routingKey; @@ -130,7 +134,7 @@ public Builder withForwardNamespace(String forward) { * Convenience method to prepare Builder with properties equal to {@literal source} properties. * Is useful for cases when almost same RequestOptions except one or two properties are needed. */ - public Builder from(RequestOptions source) { + protected Builder from(RequestOptions source) { this.ackTimeout = source.ackTimeout; this.responseTimeout = source.responseTimeout; this.waitForResponses = source.waitForResponses; diff --git a/core/src/main/java/io/github/tcdl/msb/api/ResponderOptions.java b/core/src/main/java/io/github/tcdl/msb/api/ResponderOptions.java new file mode 100644 index 00000000..b56c31d2 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/api/ResponderOptions.java @@ -0,0 +1,60 @@ +package io.github.tcdl.msb.api; + +import joptsimple.internal.Strings; +import org.apache.commons.lang3.Validate; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.Set; + +/** + * Specifies options for {@link ResponderServer} + */ +public class ResponderOptions { + + private final Set bindingKeys; + private final MessageTemplate messageTemplate; + + public static final ResponderOptions DEFAULTS = new Builder().build(); + + protected ResponderOptions(Set bindingKeys, + MessageTemplate messageTemplate) { + + this.bindingKeys = Collections.unmodifiableSet(bindingKeys); + this.messageTemplate = messageTemplate; + } + + @Nonnull + public Set getBindingKeys() { + return bindingKeys; + } + + public MessageTemplate getMessageTemplate() { + return messageTemplate; + } + + public static class Builder { + + protected Set bindingKeys; + protected MessageTemplate messageTemplate; + + /** + * Each invocation REPLACES the old set of binding keys. Last one wins. + */ + public Builder withBindingKeys(Set bindingKeys) { + this.bindingKeys = bindingKeys; + return this; + } + + public Builder withMessageTemplate(MessageTemplate responseMessageTemplate) { + this.messageTemplate = responseMessageTemplate; + return this; + } + + public ResponderOptions build() { + return new ResponderOptions( + bindingKeys == null ? Collections.singleton(Strings.EMPTY) : bindingKeys, + messageTemplate == null ? new MessageTemplate() : messageTemplate); + } + } +} diff --git a/core/src/main/java/io/github/tcdl/msb/api/exception/AdapterCreationException.java b/core/src/main/java/io/github/tcdl/msb/api/exception/AdapterCreationException.java new file mode 100644 index 00000000..a8196da7 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/api/exception/AdapterCreationException.java @@ -0,0 +1,11 @@ +package io.github.tcdl.msb.api.exception; + +public class AdapterCreationException extends MsbException { + public AdapterCreationException(String message) { + super(message); + } + + public AdapterCreationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index 22b7e3a6..1ee19e30 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -9,6 +9,7 @@ import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.events.EventHandlers; import io.github.tcdl.msb.impl.MessageContextImpl; import io.github.tcdl.msb.impl.MsbContextImpl; @@ -16,6 +17,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.concurrent.NotThreadSafe; import java.time.Clock; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -36,6 +38,7 @@ /** * {@link Collector} is a component which collects responses and acknowledgements for sent requests. */ +@NotThreadSafe public class Collector implements ConsumedMessagesAwareMessageHandler, ExecutionOptionsAwareMessageHandler { private static final Logger LOG = LoggerFactory.getLogger(Collector.class); @@ -73,6 +76,7 @@ public class Collector implements ConsumedMessagesAwareMessageHandler, Execut private ScheduledFuture ackTimeoutFuture; private ScheduledFuture responseTimeoutFuture; private final CollectorManager collectorManager; + private final MsbConfig msbConfig; /** * Count of consumed incoming messages so {@link #handleMessage} invocation is expected in future. @@ -112,6 +116,7 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt TypeReference payloadTypeReference, boolean directlyInvokableCallbacks) { this.requestMessage = requestMessage; + this.msbConfig = msbContext.getMsbConfig(); this.clock = msbContext.getClock(); this.collectorManager = msbContext.getCollectorManagerFactory().findOrCreateCollectorManager(topic); this.timeoutManager = msbContext.getTimeoutManager(); @@ -311,8 +316,9 @@ private int getMaxTimeoutMs() { for (String responderId : timeoutMsById.keySet()) { // Use only what we're waiting for if (!responsesRemainingById.isEmpty() && responsesRemainingById.containsKey(responderId) - && responsesRemainingById.get(responderId) == 0) + && responsesRemainingById.get(responderId) == 0) { continue; + } maxTimeoutMs = Math.max(timeoutMsById.get(responderId), maxTimeoutMs); } @@ -387,7 +393,7 @@ void waitForAcks() { private int getResponseTimeoutFromConfigs(RequestOptions requestOptions) { if (requestOptions.getResponseTimeout() == null) { - return 3000; + return msbConfig.getDefaultResponseTimeout(); } return requestOptions.getResponseTimeout(); } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java index 33e5850b..f5bf89c2 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java @@ -6,13 +6,13 @@ import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.monitor.aggregator.DefaultChannelMonitorAggregator; +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Type; import java.util.Collections; -import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; @@ -38,113 +38,43 @@ public Requester createRequester(String namespace, RequestOptions request return RequesterImpl.create(namespace, requestOptions, msbContext, payloadTypeReference); } - /** - * {@inheritDoc} - */ - @Override - public Requester createRequesterForSingleResponse(String namespace, Class payloadClass) { - MsbConfig msbConfig = msbContext.getMsbConfig(); - - RequestOptions requestOptions = new RequestOptions.Builder() - .withMessageTemplate(new MessageTemplate()) - .withResponseTimeout(msbConfig.getDefaultResponseTimeout()) - .build(); - - return createRequesterForSingleResponse(namespace, payloadClass, requestOptions); - } - /** * {@inheritDoc} */ @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, RequestOptions baseRequestOptions) { - - RequestOptions requestOptions = new RequestOptions.Builder().from(baseRequestOptions) + Validate.notNull(baseRequestOptions); + RequestOptions singleResponseRequestOptions = baseRequestOptions + .asBuilder() .withWaitForResponses(1) - .withAckTimeout(0) .build(); - return RequesterImpl.create(namespace, requestOptions, msbContext, toTypeReference(payloadClass)); - } - /** - * {@inheritDoc} - */ - @Override - public ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { - return ResponderServerImpl.create(namespace, routingKeys, messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference); + return RequesterImpl.create(namespace, singleResponseRequestOptions, msbContext, toTypeReference(payloadClass)); } /** * {@inheritDoc} */ @Override - public Requester createRequesterForFireAndForget(String namespace) { - return createRequesterForFireAndForget(namespace, null); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate){ - RequestOptions.Builder optionsBuilder = new RequestOptions.Builder() - .withMessageTemplate(messageTemplate) - .withWaitForResponses(0); - - return RequesterImpl.create(namespace, optionsBuilder.build(), msbContext, null); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace, String forwardTo, MessageTemplate messageTemplate) { - RequestOptions.Builder optionsBuilder = new RequestOptions.Builder() - .withForwardNamespace(forwardTo) - .withMessageTemplate(messageTemplate) - .withWaitForResponses(0); - - return RequesterImpl.create(namespace, optionsBuilder.build(), msbContext, null); - } - - /** - * {@inheritDoc} - */ - @Override - public Requester createRequesterForFireAndForget(MessageDestination destination, MessageTemplate messageTemplate) { - RequestOptions.Builder optionsBuilder = new RequestOptions.Builder() - .withMessageTemplate(messageTemplate) - .withRoutingKey(destination.getRoutingKey()) - .withWaitForResponses(0); - - return RequesterImpl.create(destination.getTopic(), optionsBuilder.build(), msbContext, null); + public ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, + ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { + return ResponderServerImpl.create(namespace, responderOptions, msbContext, requestHandler, errorHandler, payloadTypeReference); } /** * {@inheritDoc} */ @Override - public Requester createRequesterForFireAndForget(String namespace, MessageDestination forwardTo, MessageTemplate messageTemplate) { - RequestOptions.Builder optionsBuilder = new RequestOptions.Builder() - .withMessageTemplate(messageTemplate) - .withForwardNamespace(forwardTo.getTopic()) - .withRoutingKey(forwardTo.getRoutingKey()) - .withWaitForResponses(0); - - return RequesterImpl.create(namespace, optionsBuilder.build(), msbContext, null); - } + public Requester createRequesterForFireAndForget(String namespace, RequestOptions requestOptions) { + Validate.notNull(requestOptions, "RequestOptions are mandatory"); - /** - * {@inheritDoc} - */ - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) { - return createResponderServer(namespace, messageTemplate, requestHandler, null, payloadTypeReference); - } + RequestOptions fireAndForgetRequestOptions = requestOptions + .asBuilder() + .withAckTimeout(0) + .withWaitForResponses(0) + .build(); - /** - * {@inheritDoc} - */ - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { - return ResponderServerImpl.create(namespace, Collections.emptySet(), messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference); + return RequesterImpl.create(namespace, fireAndForgetRequestOptions, msbContext, null); } /** diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java index faa485fa..7416e463 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java @@ -177,13 +177,7 @@ private void publish(boolean invokeHandlersDirectly, Object requestPayload, Mess } private void publishMessage(Message message) { - Topics topics = message.getTopics(); - if (StringUtils.isBlank(topics.getForward()) && StringUtils.isNotBlank(topics.getRoutingKey())) { - MessageDestination destination = new MessageDestination(topics.getTo(), topics.getRoutingKey()); - getChannelManager().findOrCreateProducer(destination).publish(message); - } else { - getChannelManager().findOrCreateProducer(topics.getTo()).publish(message); - } + getChannelManager().findOrCreateProducer(message.getTopics().getTo(), requestOptions).publish(message); } private boolean isWaitForAckMs() { diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java index 42715a7a..d6fa9794 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java @@ -4,6 +4,7 @@ import io.github.tcdl.msb.Producer; import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.MessageTemplate; +import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Acknowledge.Builder; import io.github.tcdl.msb.api.message.Message; @@ -60,7 +61,7 @@ public void send(Object responsePayload) { } private void sendMessage(Message message) { - Producer producer = channelManager.findOrCreateProducer(message.getTopics().getTo()); + Producer producer = channelManager.findOrCreateProducer(message.getTopics().getTo(), RequestOptions.DEFAULTS); LOG.debug("Publishing message to topic : {}", message.getTopics().getTo()); producer.publish(message); } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java index 95965838..c6627a38 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java @@ -18,24 +18,22 @@ public class ResponderServerImpl implements ResponderServer { private static final Logger LOG = LoggerFactory.getLogger(ResponderServerImpl.class); private String namespace; - private Set routingKeys; + private ResponderOptions responderOptions; private MsbContextImpl msbContext; - private MessageTemplate messageTemplate; private RequestHandler requestHandler; private Optional errorHandler; private ObjectMapper payloadMapper; private TypeReference payloadTypeReference; private ResponderServerImpl(String namespace, - Set routingKeys, - MessageTemplate messageTemplate, + ResponderOptions responderOptions, MsbContextImpl msbContext, RequestHandler requestHandler, ErrorHandler errorHandler, TypeReference payloadTypeReference) { + Validate.notNull(responderOptions); this.namespace = namespace; - this.routingKeys = routingKeys; - this.messageTemplate = messageTemplate; + this.responderOptions = responderOptions; this.msbContext = msbContext; this.requestHandler = requestHandler; this.errorHandler = Optional.ofNullable(errorHandler); @@ -47,9 +45,9 @@ private ResponderServerImpl(String namespace, /** * {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(String, MessageTemplate, RequestHandler, ErrorHandler, Class)} */ - static ResponderServerImpl create(String namespace, Set routingKeys, MessageTemplate messageTemplate, MsbContextImpl msbContext, + static ResponderServerImpl create(String namespace, ResponderOptions responderOptions, MsbContextImpl msbContext, RequestHandler requestHandler, ErrorHandler errorHandler, TypeReference payloadTypeReference) { - return new ResponderServerImpl<>(namespace, routingKeys, messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference); + return new ResponderServerImpl<>(namespace, responderOptions, msbContext, requestHandler, errorHandler, payloadTypeReference); } /** @@ -71,12 +69,7 @@ public ResponderServer listen() { onResponder(responderContext); }; - if (routingKeys == null || routingKeys.isEmpty()) { - channelManager.subscribe(namespace, messageHandler); - } else { - channelManager.subscribe(namespace, routingKeys, messageHandler); - } - + channelManager.subscribe(namespace, responderOptions, messageHandler); return this; } @@ -89,7 +82,7 @@ public ResponderServer stop(){ Responder createResponder(Message incomingMessage) { if (isResponseNeeded(incomingMessage)) { - return new ResponderImpl(messageTemplate, incomingMessage, msbContext); + return new ResponderImpl(responderOptions.getMessageTemplate(), incomingMessage, msbContext); } else { return new NoopResponderImpl(incomingMessage); } diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java b/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java index cec48ae5..45f3554f 100644 --- a/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java +++ b/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java @@ -3,6 +3,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.Producer; import io.github.tcdl.msb.api.MessageTemplate; +import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; @@ -143,7 +144,7 @@ private void doAnnounce() { .withBody(topicInfoMap) .build(); - Producer producer = channelManager.findOrCreateProducer(Utils.TOPIC_ANNOUNCE); + Producer producer = channelManager.findOrCreateProducer(Utils.TOPIC_ANNOUNCE, RequestOptions.DEFAULTS); Message.Builder messageBuilder = messageFactory.createBroadcastMessageBuilder(Utils.TOPIC_ANNOUNCE, new MessageTemplate()); Message announcementMessage = messageFactory.createBroadcastMessage(messageBuilder, payload); diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java index 495befd3..dfb4e066 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java @@ -9,6 +9,7 @@ import io.github.tcdl.msb.adapters.AdapterFactory; import io.github.tcdl.msb.adapters.AdapterFactoryLoader; +import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.RequesterResponderIT; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.collector.CollectorManager; @@ -56,7 +57,7 @@ public void testProducerCachedMultithreadInteraction() { String topic = "topic:test-producer-cached-multithreaded"; new MultithreadingTester().add(() -> { - channelManager.findOrCreateProducer(topic); + channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); verify(mockChannelMonitorAgent).producerTopicCreated(topic); }).run(); } @@ -80,7 +81,7 @@ public void testPublishMessageInvokesAgentMultithreadInteraction() throws Interr int numberOfThreads = 10; int numberOfInvocationsPerThread = 20; - Producer producer = channelManager.findOrCreateProducer(topic); + Producer producer = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); Message message = TestUtils.createSimpleRequestMessage(topic); CountDownLatch messagesSent = new CountDownLatch(numberOfThreads * numberOfInvocationsPerThread); @@ -100,12 +101,12 @@ public void testReceiveMessageInvokesAgentAndEmitsEventMultithreadInteraction() int numberOfThreads = 4; int numberOfInvocationsPerThread = 20; - Producer producer = channelManager.findOrCreateProducer(topic); + Producer producer = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); Message message = TestUtils.createSimpleRequestMessage(topic); CountDownLatch messagesReceived = new CountDownLatch(numberOfThreads * numberOfInvocationsPerThread); - channelManager.findOrCreateProducer(topic); + channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); channelManager.subscribe(topic, (msg, acknowledgeHandler) -> { messagesReceived.countDown(); diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java index a9e7189d..ed36e927 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java @@ -1,36 +1,33 @@ package io.github.tcdl.msb; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - +import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.adapters.AdapterFactory; import io.github.tcdl.msb.adapters.AdapterFactoryLoader; -import io.github.tcdl.msb.api.MessageDestination; +import io.github.tcdl.msb.api.RequestOptions; +import io.github.tcdl.msb.api.ResponderOptions; import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException; -import io.github.tcdl.msb.api.exception.MsbException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.TestUtils; - -import java.time.Clock; -import java.util.Collections; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import javax.xml.ws.Holder; - import io.github.tcdl.msb.threading.ConsumerExecutorFactoryImpl; import io.github.tcdl.msb.threading.MessageHandlerInvoker; import io.github.tcdl.msb.threading.ThreadPoolMessageHandlerInvoker; import org.junit.Before; import org.junit.Rule; import org.junit.Test; - -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.rules.ExpectedException; +import javax.xml.ws.Holder; +import java.time.Clock; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + public class ChannelManagerTest { private ChannelManager channelManager; @@ -60,81 +57,47 @@ public void testProducerCached() { String topic = "topic:test-producer-cached"; // Producer was created and monitor agent notified - Producer producer1 = channelManager.findOrCreateProducer(topic); + Producer producer1 = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); assertNotNull(producer1); verify(mockChannelMonitorAgent).producerTopicCreated(topic); // Cached producer was returned and monitor agent wasn't notified - Producer producer2 = channelManager.findOrCreateProducer(topic); + Producer producer2 = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); assertNotNull(producer2); assertSame(producer1, producer2); verifyNoMoreInteractions(mockChannelMonitorAgent); } - @Test(expected = ConsumerSubscriptionException.class) - public void testConsumerSubscribeMultipleSameTopic() { - String topic = "topic:test-consumer-cached"; + @Test + public void testMultipleConsumersCantSubscribeOnTheSameTopic() { + String topic = "topic:test-consumer"; // Consumer was created and monitor agent notified channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); + expectedException.expect(ConsumerSubscriptionException.class); channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); } @Test - public void testSubscribeSameTopicDifferentRoutingKeys() throws Exception { + public void testCantSubscribeOnTheSameTopicWithDifferentRoutingKeys() throws Exception { String topic = "interesting:topic"; - String routingKey1 = "routing.key.one"; - String routingKey2 = "routing.key.two"; + String bindingKey1 = "routing.key.one"; + String bindingKey2 = "routing.key.two"; + + ResponderOptions responderOptions1 = new ResponderOptions.Builder().withBindingKeys(Collections.singleton(bindingKey1)).build(); + ResponderOptions responderOptions2 = new ResponderOptions.Builder().withBindingKeys(Collections.singleton(bindingKey2)).build(); - channelManager.subscribe(topic, Collections.singleton(routingKey1), (message, acknowledgeHandler) -> {}); + channelManager.subscribe(topic, responderOptions1, (message, acknowledgeHandler) -> {}); verify(mockChannelMonitorAgent).consumerTopicCreated(topic); expectedException.expect(ConsumerSubscriptionException.class); - channelManager.subscribe(topic, Collections.singleton(routingKey2), (message, acknowledgeHandler) -> {}); - } - - @Test - public void testFindOrCreateProducerForDestination() throws Exception { - - String topic = "interesting:topic"; - String routingKey1 = "routing.key.one"; - String routingKey2 = "routing.key.two"; - - MessageDestination destination1 = new MessageDestination(topic, routingKey1); - MessageDestination destination2 = new MessageDestination(topic, routingKey2); - - Producer producer1 = channelManager.findOrCreateProducer(destination1); - Producer producer2 = channelManager.findOrCreateProducer(destination2); - - assertEquals("Multicast producer must be the same for same topic", producer1, producer2); - } - - @Test - public void testFindOrCreateProducer_broadcastProducerAlreadyExists() throws Exception { - String topic = "interesting:topic"; - String routingKey = "routing.key"; - - MessageDestination destination = new MessageDestination(topic, routingKey); - channelManager.findOrCreateProducer(topic); - expectedException.expect(MsbException.class); - channelManager.findOrCreateProducer(destination); - } - - @Test - public void testFindOrCreateProducer_multicastProducerAlreadyExists() throws Exception { - String topic = "interesting:topic"; - String routingKey = "routing.key"; - - MessageDestination destination = new MessageDestination(topic, routingKey); - channelManager.findOrCreateProducer(destination); - expectedException.expect(MsbException.class); - channelManager.findOrCreateProducer(topic); + channelManager.subscribe(topic, responderOptions2, (message, acknowledgeHandler) -> {}); } @Test public void testPublishMessageInvokesAgent() { String topic = "topic:test-agent-publish"; - Producer producer = channelManager.findOrCreateProducer(topic); + Producer producer = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); Message message = TestUtils.createSimpleRequestMessage(topic); producer.publish(message); @@ -151,11 +114,11 @@ public void testReceiveMessageInvokesAgentAndEmitsEvent() throws InterruptedExce Message message = TestUtils.createSimpleRequestMessage(topic); channelManager.subscribe(topic, - (msg, acknowledgeHandler) -> { + (msg, acknowledgeHandler) -> { messageEvent.value = msg; awaitReceiveEvents.countDown(); }); - channelManager.findOrCreateProducer(topic).publish(message); + channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS).publish(message); assertTrue(awaitReceiveEvents.await(4000, TimeUnit.MILLISECONDS)); verify(mockChannelMonitorAgent).consumerMessageReceived(topic); @@ -166,7 +129,8 @@ public void testReceiveMessageInvokesAgentAndEmitsEvent() throws InterruptedExce public void testSubscribeUnsubscribeFromBroadcast() { String topic = "topic:test-unsubscribe-once"; - channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); + channelManager.subscribe(topic, (message, acknowledgeHandler) -> { + }); channelManager.unsubscribe(topic); verify(mockChannelMonitorAgent).consumerTopicRemoved(topic); @@ -176,7 +140,10 @@ public void testSubscribeUnsubscribeFromBroadcast() { public void testSubscribeUnsubscribeFromMulticast() { String topic = "topic:test-unsubscribe-once"; - channelManager.subscribe(topic, Collections.singleton("routing.key"), (message, acknowledgeHandler) -> {}); + ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.singleton("routing.key")).build(); + + channelManager.subscribe(topic, responderOptions, (message, acknowledgeHandler) -> { + }); channelManager.unsubscribe(topic); verify(mockChannelMonitorAgent).consumerTopicRemoved(topic); @@ -187,8 +154,10 @@ public void testSubscribeUnsubscribeSeparateTopics() { String topic1 = "topic:test-unsubscribe-try-first"; String topic2 = "topic:test-unsubscribe-try-other"; - channelManager.subscribe(topic1, (message, acknowledgeHandler) -> {}); - channelManager.subscribe(topic2, (message, acknowledgeHandler) -> {}); + channelManager.subscribe(topic1, (message, acknowledgeHandler) -> { + }); + channelManager.subscribe(topic2, (message, acknowledgeHandler) -> { + }); channelManager.unsubscribe(topic1); verify(mockChannelMonitorAgent).consumerTopicRemoved(topic1); diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index d804a7f4..b0728877 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -138,13 +138,6 @@ public void testCreateConsumerNullMessageMapper() { new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, null); } - @Test - public void testSubscribeAdapterSubscribed() { - new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); - - verify(adapterMock).subscribe(any(ConsumerAdapter.RawMessageHandler.class)); - } - @Test public void testValidMessageProcessedBySubscriber() throws JsonConversionException { Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java index dc8857a8..afc7aaa7 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java @@ -3,17 +3,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; + +import com.fasterxml.jackson.core.type.TypeReference; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.impl.MsbContextImpl; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java index 5ee34d7e..0f1e1f9b 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java @@ -1065,7 +1065,7 @@ public void testProcessAckPerResponder() { @Test public void testProcessAckWillTakeDefaultTimeoutIsMaxAndNotCallTimerAgain() { int timeoutMs = 1500; - //will set default 3000; + when(msbConfigurationsMock.getDefaultResponseTimeout()).thenReturn(3000); when(requestOptionsMock.getResponseTimeout()).thenReturn(null); Collector collector = createCollector(); @@ -1207,13 +1207,13 @@ MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandle private void notifyMessagesConsumed(Collector collector, int messagesCount) { for(int i = 0; i < messagesCount; i++) { - ((ConsumedMessagesAwareMessageHandler)collector).notifyMessageConsumed(); + collector.notifyMessageConsumed(); } } private void notifyMessagesLost(Collector collector, int messagesCount) { for(int i = 0; i < messagesCount; i++) { - ((ConsumedMessagesAwareMessageHandler)collector).notifyConsumedMessageIsLost(); + collector.notifyConsumedMessageIsLost(); } } diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index 28e9f63d..84ce7e63 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -24,14 +24,12 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.*; /** * Created by rdro on 4/27/2015. @@ -39,8 +37,7 @@ @RunWith(MockitoJUnitRunner.class) public class RequesterImplTest { - private static final String BROADCAST_NAMESPACE = "test:hello:all"; - private static final String MULTICAST_NAMESPACE = "test:hello:everyone"; + private static final String TOPIC = "test:hello:all"; @Mock private ChannelManager channelManagerMock; @@ -64,16 +61,6 @@ public void testPublishNoWaitForResponses() throws Exception { verify(collectorMock, never()).waitForResponses(); } - @Test - public void testPublishWithRoutingKeyNoWaitForResponses() throws Exception { - RequesterImpl requester = initRequesterForResponsesWith("UK", 0, 0, 0, null, null, null, null); - - publishByAllMethods(requester); - - verify(collectorMock, never()).listenForResponses(); - verify(collectorMock, never()).waitForResponses(); - } - @Test public void testPublishWaitForResponses() throws Exception { RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); @@ -84,16 +71,6 @@ public void testPublishWaitForResponses() throws Exception { verify(collectorMock, times(4)).waitForResponses(); } - @Test - public void testPublishWithRoutingKeyWaitForResponses() throws Exception { - RequesterImpl requester = initRequesterForResponsesWith("UK", 1, 0, 0, null, null, null, null); - - publishByAllMethods(requester); - - verify(collectorMock, times(4)).listenForResponses(); - verify(collectorMock, times(4)).waitForResponses(); - } - private void publishByAllMethods(RequesterImpl requester) { Message originalMessage = TestUtils.createMsbRequestMessage("some:topic", "body text"); requester.publish(TestUtils.createSimpleRequestPayload()); @@ -352,7 +329,7 @@ public void testRequest_acknowledgeHandlerCancelsFutureOnTooManyResponses() thro public void testRequestMessage() throws Exception { ChannelManager channelManagerMock = mock(ChannelManager.class); Producer producerMock = mock(Producer.class); - when(channelManagerMock.findOrCreateProducer(BROADCAST_NAMESPACE)).thenReturn(producerMock); + when(channelManagerMock.findOrCreateProducer(eq(TOPIC), eq(RequestOptions.DEFAULTS))).thenReturn(producerMock); ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); MsbContextImpl msbContext = TestUtils.createMsbContextBuilder() @@ -361,7 +338,7 @@ public void testRequestMessage() throws Exception { .build(); RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); - Requester requester = RequesterImpl.create(BROADCAST_NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference(){}); + Requester requester = RequesterImpl.create(TOPIC, RequestOptions.DEFAULTS, msbContext, new TypeReference(){}); requester.publish(requestPayload); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -375,7 +352,6 @@ public void testRequestMessage() throws Exception { public void testRequestMessageWithTags() throws Exception { ChannelManager channelManagerMock = mock(ChannelManager.class); Producer producerMock = mock(Producer.class); - when(channelManagerMock.findOrCreateProducer(BROADCAST_NAMESPACE)).thenReturn(producerMock); ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); MsbContextImpl msbContext = TestUtils.createMsbContextBuilder() @@ -390,7 +366,9 @@ public void testRequestMessageWithTags() throws Exception { RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); RequestOptions requestOptions = TestUtils.createSimpleRequestOptionsWithTags(tag); - Requester requester = RequesterImpl.create(BROADCAST_NAMESPACE, requestOptions, msbContext, new TypeReference(){}); + when(channelManagerMock.findOrCreateProducer(eq(TOPIC), eq(requestOptions))).thenReturn(producerMock); + + Requester requester = RequesterImpl.create(TOPIC, requestOptions, msbContext, new TypeReference(){}); requester.publish(requestPayload, dynamicTag1, dynamicTag2, nullTag); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -398,42 +376,6 @@ public void testRequestMessageWithTags() throws Exception { assertArrayEquals(new String[]{tag, dynamicTag1, dynamicTag2}, requestMessage.getTags().toArray()); } - @Test - public void testRequestMessageWithForward_shouldNotWaitForResponsesOrAcks() throws Exception { - String routingKey = "to.santa"; - - ChannelManager channelManagerMock = mock(ChannelManager.class); - Producer producerMock = mock(Producer.class); - when(channelManagerMock.findOrCreateProducer(BROADCAST_NAMESPACE)).thenReturn(producerMock); - ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); - - MsbContextImpl msbContext = TestUtils.createMsbContextBuilder() - .withChannelManager(channelManagerMock) - .withClock(Clock.systemDefaultZone()) - .build(); - - RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); - RequestOptions requestOptions = new RequestOptions - .Builder() - .withRoutingKey(routingKey) - .withWaitForResponses(3) - .withAckTimeout(1) - .withForwardNamespace(MULTICAST_NAMESPACE).build(); - - RequesterImpl requesterSpy = spy(RequesterImpl.create(BROADCAST_NAMESPACE, requestOptions, msbContext, new TypeReference() {})); - requesterSpy.publish(requestPayload); - - verify(producerMock).publish(messageArgumentCaptor.capture()); - - //check collector wasn't set up - verify(requesterSpy, never()).createCollector(any(), any(), any(), any(), anyBoolean()); - verify(channelManagerMock, never()).findOrCreateProducer(any(MessageDestination.class)); - - //check message fields - Message requestMessage = messageArgumentCaptor.getValue(); - assertEquals(MULTICAST_NAMESPACE, requestMessage.getTopics().getForward()); - assertEquals(routingKey, requestMessage.getTopics().getRoutingKey()); - } private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout, Integer ackTimeout, BiConsumer onResponse, BiConsumer onAcknowledge, @@ -447,27 +389,9 @@ private RequesterImpl initRequesterForResponsesWith(Integer numberO .withAckTimeout(ackTimeout) .build(); - when(channelManagerMock.findOrCreateProducer(anyString())).thenReturn(producerMock); - - return setUpRequester(BROADCAST_NAMESPACE, onResponse, onAcknowledge, onError, endHandler, requestOptions); - } - - private RequesterImpl initRequesterForResponsesWith(String routingKey, Integer numberOfResponses, Integer respTimeout, Integer ackTimeout, - BiConsumer onResponse, BiConsumer onAcknowledge, - BiConsumer onError, - Callback endHandler) throws Exception { - - RequestOptions requestOptions = new RequestOptions.Builder() - .withMessageTemplate(mock(MessageTemplate.class)) - .withWaitForResponses(numberOfResponses) - .withResponseTimeout(respTimeout) - .withRoutingKey(routingKey) - .withAckTimeout(ackTimeout) - .build(); - - when(channelManagerMock.findOrCreateProducer(any(MessageDestination.class))).thenReturn(producerMock); + when(channelManagerMock.findOrCreateProducer(anyString(), any(RequestOptions.class))).thenReturn(producerMock); - return setUpRequester(MULTICAST_NAMESPACE, onResponse, onAcknowledge, onError, endHandler, requestOptions); + return setUpRequester(TOPIC, onResponse, onAcknowledge, onError, endHandler, requestOptions); } private RequesterImpl setUpRequester(String namespace, BiConsumer onResponse, BiConsumer onAcknowledge, BiConsumer onError, Callback endHandler, RequestOptions requestOptions) { diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java index 65ccb2b4..a5167c6e 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; @@ -14,6 +15,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.Producer; import io.github.tcdl.msb.api.MessageTemplate; +import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.config.MsbConfig; @@ -56,7 +58,7 @@ public void setUp() { when(msbContextSpy.getChannelManager()).thenReturn(mockChannelManager); when(msbContextSpy.getMessageFactory()).thenReturn(spyMessageFactory); - when(mockChannelManager.findOrCreateProducer(anyString())).thenReturn(mockProducer); + when(mockChannelManager.findOrCreateProducer(anyString(), any(RequestOptions.class))).thenReturn(mockProducer); responder = new ResponderImpl(messageTemplate, originalMessage, msbContextSpy); } @@ -73,7 +75,7 @@ public void testProducerWasCreatedForProperTopic() { ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); responder.send(""); - verify(mockChannelManager).findOrCreateProducer(argument.capture()); + verify(mockChannelManager).findOrCreateProducer(argument.capture(), any(RequestOptions.class)); assertEquals(originalMessage.getTopics().getResponse(), argument.getValue()); } diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java index 78f7d45d..ffbf4494 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java @@ -5,12 +5,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.Producer; -import io.github.tcdl.msb.api.AcknowledgementHandler; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.Responder; -import io.github.tcdl.msb.api.ResponderContext; -import io.github.tcdl.msb.api.ResponderServer; +import io.github.tcdl.msb.api.*; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.support.TestUtils; @@ -28,12 +23,7 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class ResponderServerImplTest { @@ -69,13 +59,15 @@ public void testResponderServerProcessPayloadSuccess() throws Exception { when(spyMsbContext.getChannelManager()).thenReturn(spyChannelManager); - ResponderServerImpl, Object, Map>> responderServer = ResponderServerImpl - .create(TOPIC, Collections.emptySet(), requestOptions.getMessageTemplate(), spyMsbContext, handler, null, + ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.emptySet()).withMessageTemplate(messageTemplate).build(); + + ResponderServerImpl, Object, Map>> responderServer = + ResponderServerImpl.create(TOPIC,responderOptions, spyMsbContext, handler, null, new TypeReference, Object, Map>>() {}); ResponderServerImpl spyResponderServer = (ResponderServerImpl) spy(responderServer).listen(); - verify(spyChannelManager).subscribe(anyString(), subscriberCaptor.capture()); + verify(spyChannelManager).subscribe(anyString(), any(ResponderOptions.class), subscriberCaptor.capture()); assertNull("MessageContext must be absent outside message handler execution", MsbThreadContext.getMessageContext()); assertNull("Request must be absent outside message handler execution", MsbThreadContext.getRequest()); @@ -98,8 +90,10 @@ public void testResponderServerProcessUnexpectedPayload() throws Exception { String bodyText = "some body"; Message incomingMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, null, messageTemplate, msbContext, handler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, handler, null, new TypeReference() {}); responderServer.listen(); // simulate incoming request @@ -119,8 +113,10 @@ public void testResponderServerProcessHandlerThrowException() throws Exception { Exception error = new Exception(exceptionMessage); ResponderServer.RequestHandler handler = (request, responderContext) -> { throw error; }; + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, null, messageTemplate, msbContext, handler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, handler, null, new TypeReference() {}); responderServer.listen(); // simulate incoming request @@ -143,9 +139,10 @@ public void testResponderServerProcessCustomHandlerThrowException() throws Excep throw error; }; + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); ResponderServer.ErrorHandler errorHandlerMock = mock(ResponderServer.ErrorHandler.class); ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, null, messageTemplate, msbContext, handler, errorHandlerMock, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, handler, errorHandlerMock, new TypeReference() {}); responderServer.listen(); // simulate incoming request @@ -166,13 +163,14 @@ public void testCreateResponderWithResponseTopic() { ChannelManager mockChannelManager = mock(ChannelManager.class); Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(anyString())).thenReturn(mockProducer); + when(mockChannelManager.findOrCreateProducer(anyString(), any(RequestOptions.class))).thenReturn(mockProducer); MsbContextImpl msbContext1 = new TestUtils.TestMsbContextBuilder() .withChannelManager(mockChannelManager) .build(); + ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.emptySet()).withMessageTemplate(messageTemplate).build(); ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, null, messageTemplate, msbContext1, handler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext1, handler, null, new TypeReference() {}); Message incomingMessage = TestUtils.createMsbRequestMessageNoPayload(TOPIC); Responder responder = responderServer.createResponder(incomingMessage); @@ -194,14 +192,16 @@ public void testCreateResponderWithRoutingKeys() throws Exception { .withChannelManager(mockChannelManager) .build(); - Set routingKeys = Sets.newHashSet("routing.key.one", "routing.key.two"); + Set bindingKeys = Sets.newHashSet("routing.key.one", "routing.key.two"); ResponderServer.RequestHandler requestHandler = (request, responderContext) -> {}; + + ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(bindingKeys).withMessageTemplate(messageTemplate).build(); ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, routingKeys, messageTemplate, msbContext, requestHandler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, requestHandler, null, new TypeReference() {}); responderServer.listen(); - verify(mockChannelManager).subscribe(eq(TOPIC), eq(routingKeys), any(MessageHandler.class)); + verify(mockChannelManager).subscribe(eq(TOPIC), eq(responderOptions), any(MessageHandler.class)); } @Test @@ -213,11 +213,14 @@ public void testCreateResponderWithoutRoutingKeys() throws Exception { .build(); ResponderServer.RequestHandler requestHandler = (request, responderContext) -> {}; + + ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.emptySet()).withMessageTemplate(messageTemplate).build(); + ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, Collections.emptySet(), messageTemplate, msbContext, requestHandler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, requestHandler, null, new TypeReference() {}); responderServer.listen(); - verify(mockChannelManager).subscribe(eq(TOPIC), any(MessageHandler.class)); + verify(mockChannelManager).subscribe(eq(TOPIC), same(responderOptions), any(MessageHandler.class)); } @Test @@ -230,8 +233,10 @@ public void testCreateResponderNoResponseTopic() { .withChannelManager(mockChannelManager) .build(); + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, null, messageTemplate, msbContext, handler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, handler, null, new TypeReference() {}); Message incomingMessage = TestUtils.createMsbBroadcastMessageNoPayload(TOPIC); Responder responder = responderServer.createResponder(incomingMessage); @@ -252,8 +257,10 @@ public void testStop() throws Exception { .withChannelManager(mockChannelManager) .build(); + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + ResponderServerImpl responderServer = ResponderServerImpl.create( - TOPIC, null, messageTemplate, msbContext, doNothingHandler, null, new TypeReference() {} + TOPIC, responderOptions, msbContext, doNothingHandler, null, new TypeReference() {} ); responderServer.stop(); diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java index 30b92e12..ccfcf3f8 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java @@ -3,11 +3,9 @@ import io.github.tcdl.msb.adapters.AdapterFactory; import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.adapters.ProducerAdapter; -import io.github.tcdl.msb.api.MessageDestination; +import io.github.tcdl.msb.api.RequestOptions; +import io.github.tcdl.msb.api.ResponderOptions; import io.github.tcdl.msb.config.MsbConfig; -import org.apache.commons.lang3.StringUtils; - -import java.util.Set; /** * This AdapterFactory implementation is used to capture/submit raw messages as JSON and could be used during testing. @@ -30,17 +28,9 @@ public void init(MsbConfig msbConfig) { } @Override - public ProducerAdapter createProducerAdapter(String namespace) { - TestMsbProducerAdapter producerAdapter = new TestMsbProducerAdapter(namespace, storage); - storage.addProducerAdapter(namespace, producerAdapter); - return producerAdapter; - } - - @Override - public ProducerAdapter createProducerAdapter(MessageDestination destination) { - String namespace = destination.getTopic(); - TestMsbProducerAdapter producerAdapter = new TestMsbProducerAdapter(namespace, storage); - storage.addProducerAdapter(namespace, producerAdapter); + public ProducerAdapter createProducerAdapter(String topic, RequestOptions requestOptions) { + TestMsbProducerAdapter producerAdapter = new TestMsbProducerAdapter(topic, storage); + storage.addProducerAdapter(topic, producerAdapter); return producerAdapter; } @@ -52,9 +42,9 @@ public ConsumerAdapter createConsumerAdapter(String namespace, boolean isRespons } @Override - public ConsumerAdapter createConsumerAdapter(String namespace, Set routingKeys) { - TestMsbConsumerAdapter consumerAdapter = new TestMsbConsumerAdapter(namespace, storage); - storage.addConsumerAdapter(namespace, routingKeys, consumerAdapter); + public ConsumerAdapter createConsumerAdapter(String topic, ResponderOptions responderOptions, boolean isResponseTopic) { + TestMsbConsumerAdapter consumerAdapter = new TestMsbConsumerAdapter(topic, storage); + storage.addConsumerAdapter(topic, responderOptions.getBindingKeys(), consumerAdapter); return consumerAdapter; } diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java index 5de65bb8..65066222 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java @@ -6,6 +6,7 @@ import io.github.tcdl.msb.api.monitor.AggregatorStats; import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; import io.github.tcdl.msb.impl.RequesterImpl; +import org.apache.commons.lang3.Validate; import java.lang.reflect.Type; import java.util.Set; @@ -29,20 +30,11 @@ public Requester createRequester(String namespace, RequestOptions request return capture.getRequesterMock(); } - @Override - public Requester createRequesterForSingleResponse(String namespace, Class payloadClass) { - RequestOptions requestOptions = new RequestOptions.Builder() - .withMessageTemplate(new MessageTemplate()) - .withResponseTimeout(100) - .build(); - - return createRequesterForSingleResponse(namespace, payloadClass, requestOptions); - } - @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, RequestOptions baseRequestOptions) { - - RequestOptions requestOptions = new RequestOptions.Builder().from(baseRequestOptions) + Validate.notNull(baseRequestOptions); + RequestOptions requestOptions = baseRequestOptions + .asBuilder() .withWaitForResponses(1) .withAckTimeout(0) .build(); @@ -65,95 +57,18 @@ public Requester createRequester(String namespace, RequestOptions request } @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) { - ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, payloadTypeReference, null); - storage.addCapture(capture); - return capture.getResponderServerMock(); - } - - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { - ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, errorHandler, payloadTypeReference, null); - storage.addCapture(capture); - return capture.getResponderServerMock(); - } - - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler) { - ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, null, null); - storage.addCapture(capture); - return capture.getResponderServerMock(); - } - - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, Class payloadClass) { - ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, null, payloadClass); + public ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { + ResponderCapture capture = new ResponderCapture<>(namespace, responderOptions.getBindingKeys(), responderOptions.getMessageTemplate(), requestHandler, null, payloadTypeReference, null); storage.addCapture(capture); return capture.getResponderServerMock(); } @Override - public ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, - TypeReference payloadTypeReference) { + public Requester createRequesterForFireAndForget(String namespace, RequestOptions requestOptions) { + Validate.notNull(requestOptions); + RequestOptions fireAndForgetRequestOptions = requestOptions.asBuilder().withWaitForResponses(0).build(); - ResponderCapture capture = new ResponderCapture<>(namespace, routingKeys, messageTemplate, requestHandler, null, payloadTypeReference, null); - storage.addCapture(capture); - return capture.getResponderServerMock(); - } - - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) { - ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, errorHandler, null, payloadClass); - storage.addCapture(capture); - return capture.getResponderServerMock(); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace) { - return createRequesterForFireAndForget(namespace, null); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate) { - RequestOptions requestOptions = new RequestOptions.Builder() - .withWaitForResponses(0) - .build(); - return createRequester(namespace, requestOptions, (Class)null); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace, String forwardTo, MessageTemplate messageTemplate) { - RequestOptions.Builder optionsBuilder = new RequestOptions.Builder() - .withForwardNamespace(forwardTo) - .withMessageTemplate(messageTemplate) - .withWaitForResponses(0); - - return createRequester(namespace, optionsBuilder.build(), (Class)null); - } - - @Override - public Requester createRequesterForFireAndForget(MessageDestination destination, MessageTemplate messageTemplate) { - RequestOptions requestOptions = new RequestOptions.Builder() - .withWaitForResponses(0) - .withRoutingKey(destination.getRoutingKey()) - .build(); - return createRequester(destination.getTopic(), requestOptions, (Class)null); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace, MessageDestination forwardTo, MessageTemplate messageTemplate) { - RequestOptions requestOptions = new RequestOptions.Builder() - .withWaitForResponses(0) - .withForwardNamespace(forwardTo.getTopic()) - .withRoutingKey(forwardTo.getRoutingKey()) - .build(); - return createRequester(namespace, requestOptions, (Class)null); + return createRequester(namespace, fireAndForgetRequestOptions, (Class) null); } @Override diff --git a/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java b/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java index 984be173..ffde68e4 100644 --- a/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java +++ b/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java @@ -3,6 +3,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.Producer; +import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.collector.TimeoutManager; import io.github.tcdl.msb.config.ServiceDetails; @@ -25,7 +26,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -57,14 +60,14 @@ public void setUp() { public void testAnnounceProducerForServiceTopic() { channelMonitorAgent.producerTopicCreated(TOPIC_ANNOUNCE); - verify(mockChannelManager, never()).findOrCreateProducer(anyString()); + verify(mockChannelManager, never()).findOrCreateProducer(anyString(), any(RequestOptions.class)); } @Test public void testAnnounceProducerForNormalTopic() { String topicName = "search:parsers:facets:v1"; Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(TOPIC_ANNOUNCE)).thenReturn(mockProducer); + when(mockChannelManager.findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class))).thenReturn(mockProducer); // method under test channelMonitorAgent.producerTopicCreated(topicName); @@ -80,14 +83,14 @@ public void testAnnounceProducerForNormalTopic() { @Test public void testAnnounceConsumerForServiceTopic() { channelMonitorAgent.consumerTopicCreated(TOPIC_ANNOUNCE); - verify(mockChannelManager, never()).subscribe(anyString(), Mockito.any(MessageHandler.class)); + verify(mockChannelManager, never()).subscribe(anyString(), any(MessageHandler.class)); } @Test public void testAnnounceConsumerForNormalTopic() { String topicName = "search:parsers:facets:v1"; Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(TOPIC_ANNOUNCE)).thenReturn(mockProducer); + when(mockChannelManager.findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class))).thenReturn(mockProducer); // method under test channelMonitorAgent.consumerTopicCreated(topicName); @@ -104,7 +107,7 @@ public void testAnnounceConsumerForNormalTopic() { public void testRemoveConsumerForNormalTopic() { String topicName = "search:parsers:facets:v1"; Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(TOPIC_ANNOUNCE)).thenReturn(mockProducer); + when(mockChannelManager.findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class))).thenReturn(mockProducer); channelMonitorAgent.consumerTopicCreated(topicName); // subscribe to the topic as a preparation step // method under test @@ -139,11 +142,11 @@ public void testStart() { ChannelMonitorAgent startedAgent = channelMonitorAgent.start(); assertSame(channelMonitorAgent, startedAgent); - verify(mockChannelManager).subscribe(Mockito.eq(TOPIC_HEARTBEAT), Mockito.any(MessageHandler.class)); + verify(mockChannelManager).subscribe(eq(TOPIC_HEARTBEAT), any(MessageHandler.class)); } private Message verifyProducerInvokedAndReturnMessage(Producer mockProducer) { - verify(mockChannelManager).findOrCreateProducer(TOPIC_ANNOUNCE); + verify(mockChannelManager).findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class)); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); verify(mockProducer).publish(messageCaptor.capture()); return messageCaptor.getValue(); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java b/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java index a41acf66..64025572 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java @@ -18,15 +18,19 @@ public static void main(String[] args) { enableShutdownHook(true). build(); - MessageTemplate messageTemplate = new MessageTemplate(); ObjectFactory objectFactory = msbContext.getObjectFactory(); + + ResponderOptions responderOptions = new AmqpResponderOptions.Builder() + .withBindingKeys(Sets.newHashSet("zero", "two")) + .withExchangeType(ExchangeType.TOPIC) + .build(); + ResponderServer responderServer = objectFactory.createResponderServer("routing:namespace", - Sets.newHashSet("zero", "two"), - messageTemplate, + responderOptions, (request, responderContext) -> { LOG.info("Received message: {}", request); - }); + }, String.class); responderServer.listen(); } } diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java b/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java index 172ccf50..109850f7 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java @@ -1,9 +1,6 @@ package io.github.tcdl.msb.examples; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.MsbContext; -import io.github.tcdl.msb.api.MsbContextBuilder; -import io.github.tcdl.msb.api.RequestOptions; +import io.github.tcdl.msb.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +37,8 @@ public static void main(String[] args) { Runnable task = () -> { String routingKey = tikToRoutingKey.get(tik.getAndIncrement() % 3); - RequestOptions requestOptions = new RequestOptions.Builder() + RequestOptions requestOptions = new AmqpRequestOptions.Builder() + .withExchangeType(ExchangeType.TOPIC) .withRoutingKey(routingKey) .withMessageTemplate(messageTemplate) .build(); From fcfd7c629e7cdc98fa06fe6588de482be1d0f58b Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 19 Oct 2016 18:01:14 +0300 Subject: [PATCH 156/226] Removed deprecated API usage from examples + minor cleanup --- .../io/github/tcdl/msb/acceptance/MsbTestHelper.java | 10 ++-------- .../msb/acceptance/MultipleRequesterResponder.java | 5 +++-- .../tcdl/msb/acceptance/MultipleResponder.java | 8 +++----- .../io/github/tcdl/msb/examples/DateExtractor.java | 9 ++++----- .../github/tcdl/msb/examples/FacetsAggregator.java | 12 ++++-------- .../io/github/tcdl/msb/examples/PongService.java | 3 ++- .../tcdl/msb/examples/ProducerWithRoutingKey.java | 2 +- 7 files changed, 19 insertions(+), 30 deletions(-) diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java index 2f1db527..2fd9258d 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java @@ -139,20 +139,14 @@ public CompletableFuture sendForResult(Requester requester, Object pay return requester.request(payload, tags); } - public ResponderServer createResponderServer(String namespace, ResponderServer.RequestHandler requestHandler) { - return createResponderServer(DEFAULT_CONTEXT_NAME, namespace, requestHandler); - } - public ResponderServer createResponderServer(String contextName, String namespace, ResponderServer.RequestHandler requestHandler) { - MessageTemplate options = new MessageTemplate(); System.out.println(">>> RESPONDER SERVER on: " + namespace); - return getContext(contextName).getObjectFactory().createResponderServer(namespace, options, requestHandler, RestPayload.class); + return getContext(contextName).getObjectFactory().createResponderServer(namespace, ResponderOptions.DEFAULTS, requestHandler, RestPayload.class); } public ResponderServer createResponderServer(String namespace, ResponderServer.RequestHandler requestHandler, Class payloadClass) { - MessageTemplate options = new MessageTemplate(); System.out.println(">>> RESPONDER SERVER on: " + namespace); - return getDefaultContext().getObjectFactory().createResponderServer(namespace, options, requestHandler, payloadClass); + return getDefaultContext().getObjectFactory().createResponderServer(namespace, ResponderOptions.DEFAULTS, requestHandler, payloadClass); } public void shutdown(String contextName) { diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java index 9fc799c0..ba43b4df 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java @@ -1,5 +1,6 @@ package io.github.tcdl.msb.acceptance; +import com.google.common.base.Joiner; import io.github.tcdl.msb.api.Requester; import java.util.List; @@ -7,7 +8,6 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.concurrent.BasicThreadFactory; public class MultipleRequesterResponder { @@ -32,7 +32,8 @@ public void runMultipleRequesterResponder() { util.createResponderServer(responderNamespace, (request, responderContext) -> { System.out.print(">>> REQUEST: " + request); Runnable onFinalResponse = () -> { - responderContext.getResponder().send("response from MultipleRequesterResponder:" + StringUtils.join(responseBodies)); + String responses = Joiner.on(StringUtils.EMPTY).join(responseBodies); + responderContext.getResponder().send("response from MultipleRequesterResponder:" + responses); }; createAndRunRequester(requesterNamespace1, onFinalResponse); createAndRunRequester(requesterNamespace2, onFinalResponse); diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java index 9a632e3e..6d405798 100644 --- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java +++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java @@ -1,14 +1,13 @@ package io.github.tcdl.msb.acceptance; -import io.github.tcdl.msb.api.MessageTemplate; +import com.fasterxml.jackson.core.type.TypeReference; import io.github.tcdl.msb.api.MsbContext; import io.github.tcdl.msb.api.MsbContextBuilder; +import io.github.tcdl.msb.api.ResponderOptions; import io.github.tcdl.msb.api.message.payload.RestPayload; import java.util.Map; -import com.fasterxml.jackson.core.type.TypeReference; - public class MultipleResponder { public static void main(String... args) { @@ -19,8 +18,7 @@ public static void main(String... args) { } public static void runResponder(String namespace, MsbContext msbContext) { - MessageTemplate options = new MessageTemplate(); - msbContext.getObjectFactory().createResponderServer(namespace, options, (request, responderContext) -> { + msbContext.getObjectFactory().createResponderServer(namespace, ResponderOptions.DEFAULTS, (request, responderContext) -> { Map requestBody = request.getBody(); System.out.println(">>> GOT request: " + requestBody); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java b/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java index 48f5940b..2b35d82f 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java @@ -1,9 +1,6 @@ package io.github.tcdl.msb.examples; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.MsbContext; -import io.github.tcdl.msb.api.MsbContextBuilder; -import io.github.tcdl.msb.api.Responder; +import io.github.tcdl.msb.api.*; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.examples.payload.Query; import io.github.tcdl.msb.examples.payload.Request; @@ -31,7 +28,9 @@ public void start(MsbContext msbContext) { MessageTemplate messageTemplate = new MessageTemplate().withTags("date-extractor"); final String namespace = "search:parsers:facets:v1"; - msbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, responderContext) -> { + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + + msbContext.getObjectFactory().createResponderServer(namespace, responderOptions, (request, responderContext) -> { Query query = request.getQuery(); String queryString = query.getQ(); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java b/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java index 6ff5dcb4..a6e663b1 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java @@ -1,12 +1,6 @@ package io.github.tcdl.msb.examples; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.MsbContext; -import io.github.tcdl.msb.api.MsbContextBuilder; -import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.Requester; -import io.github.tcdl.msb.api.Responder; -import io.github.tcdl.msb.api.ResponderContext; +import io.github.tcdl.msb.api.*; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.examples.payload.Request; @@ -36,8 +30,10 @@ public static void main(String[] args) throws ScriptException, FileNotFoundExcep MessageTemplate messageTemplate = new MessageTemplate().withTags("facets-aggregator"); final String namespace = "search:aggregator:facets:v1"; + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + msbContext.getObjectFactory().createResponderServer( - namespace, messageTemplate, (Request facetsRequest, ResponderContext responderContext) -> { + namespace, responderOptions, (Request facetsRequest, ResponderContext responderContext) -> { String q = facetsRequest.getQuery().getQ(); Responder responder = responderContext.getResponder(); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java b/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java index 338fbfb5..c1e4b65c 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java @@ -14,7 +14,8 @@ public static void main(String[] args) { ObjectFactory objectFactory = msbContext.getObjectFactory(); MessageTemplate messageTemplate = new MessageTemplate().withTags("pong-static-tag"); - ResponderServer responderServer = objectFactory.createResponderServer("pingpong:namespace", messageTemplate, + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + ResponderServer responderServer = objectFactory.createResponderServer("pingpong:namespace", responderOptions, (request, responderContext) -> { // Response handling logic LOG.info(String.format("Handling %s...", request)); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java b/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java index 109850f7..8efa4fd3 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java @@ -13,7 +13,7 @@ import java.util.concurrent.atomic.AtomicInteger; /** - * Run this consumer in conjunction with {@link ConsumerWithRoutingKeys} to see that only messages sent with + * Run this producer in conjunction with {@link ConsumerWithRoutingKeys} to see that only messages sent with * routing keys 'zero' and 'two' are consumed and the messages sent with routing key 'one' are not. */ public class ProducerWithRoutingKey { From 36bb69b44781c9d2e117b4561999547d4c60fd56 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 21 Oct 2016 18:12:39 +0300 Subject: [PATCH 157/226] Fixed potential deadlock in AmqpAutoRecoveringChannel on channel shutdown --- .../amqp/AmqpAutoRecoveringChannel.java | 101 ++++++++++++------ .../adapters/amqp/AmqpProducerAdapter.java | 4 +- .../amqp/AmqpAutoRecoveringChannelTest.java | 57 ++++++++++ 3 files changed, 127 insertions(+), 35 deletions(-) create mode 100644 amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannelTest.java diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java index 46c59d3b..9be3dd03 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java @@ -3,11 +3,15 @@ import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.ConfirmListener; +import io.github.tcdl.msb.api.exception.ChannelException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; /** * Wrapper for {@link Channel} that support automatic re-initialization upon errors. @@ -16,51 +20,79 @@ public class AmqpAutoRecoveringChannel { private static final Logger LOG = LoggerFactory.getLogger(AmqpAutoRecoveringChannel.class); private AmqpConnectionManager connectionManager; - private volatile Channel channel; /** * Lock object used for 2 purposes: * 1. Prevent interleaving of basicPublish with confirmSelect * 2. Prevent interleaving of channel initialization and shutdown */ - private final Object lock = new Object(); + private final AtomicReference channelReference = new AtomicReference<>(); public AmqpAutoRecoveringChannel(AmqpConnectionManager connectionManager) { this.connectionManager = connectionManager; } - public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, - Map arguments) throws IOException { - Channel channel = obtainChannelForPublisherConfirms(); - return channel.exchangeDeclare(exchange, type, durable, autoDelete, arguments); + public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map arguments) { + LOG.debug("Declaring exchange. Name = [{}], type = [{}], durable = [{}], autoDelete = [{}], args = [{}].", + exchange, type, durable, autoDelete, arguments); + + return atomicOperationOnChannel(channel -> { + try { + return channel.exchangeDeclare(exchange, type, durable, autoDelete, arguments); + } catch (IOException e) { + throw new ChannelException("exchange.declare call failed", e); + } + }); + } + + public void basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body) { + LOG.debug("Publishing message. Exchange name = [{}], routing key = [{}]", exchange, routingKey); + atomicOperationOnChannel(channel -> { + try { + channel.basicPublish(exchange, routingKey, props, body); + } catch (IOException e) { + throw new ChannelException("basic.publish call failed", e); + } + }); } - public void basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body) throws IOException { - synchronized (lock) { - Channel channel = obtainChannelForPublisherConfirms(); - channel.basicPublish(exchange, routingKey, props, body); + private T atomicOperationOnChannel(Function operation) { + synchronized (channelReference) { + Channel channel = getOpenChannel(); + return operation.apply(channel); } } - private Channel obtainChannelForPublisherConfirms() throws IOException { - synchronized (lock) { - if (channel == null || !channel.isOpen()) { - createChannelForPublisherConfirms(connectionManager); - } - return channel; + private void atomicOperationOnChannel(Consumer operation) { + synchronized (channelReference) { + Channel channel = getOpenChannel(); + operation.accept(channel); } } - private void createChannelForPublisherConfirms(AmqpConnectionManager connectionManager) throws IOException { + private Channel getOpenChannel() { + return channelReference.updateAndGet(currentChannel -> { + if (currentChannel == null) { + return newChannelWithPublisherConfirms(); + } else if (!currentChannel.isOpen()) { + closeChannel(currentChannel); + return newChannelWithPublisherConfirms(); + } else { + return currentChannel; + } + }); + } - if (channel != null) { - closeChannel(channel); + private Channel newChannelWithPublisherConfirms() { + Channel newChannel; + try { + newChannel = connectionManager.obtainConnection().createChannel(); + newChannel.confirmSelect(); + } catch (IOException e) { + throw new ChannelException("Channel creation failed", e); } - channel = connectionManager.obtainConnection().createChannel(); - channel.confirmSelect(); - - channel.addConfirmListener(new ConfirmListener() { + newChannel.addConfirmListener(new ConfirmListener() { @Override public void handleAck(long deliveryTag, boolean multiple) throws IOException { LOG.debug("Processing publisher ack (deliveryTag = {}, multiple = {})", deliveryTag, multiple); @@ -72,28 +104,31 @@ public void handleNack(long deliveryTag, boolean multiple) throws IOException { } }); - channel.addShutdownListener(cause -> { - synchronized (lock) { - LOG.debug("Handling channel shutdown..."); - if (cause.isInitiatedByApplication()) { - LOG.debug("Shutdown is initiated by application. Ignoring it."); - } else { - LOG.error("Shutdown is NOT initiated by application. Resetting the channel.", cause); + newChannel.addShutdownListener(cause -> { + LOG.debug("Handling channel shutdown..."); + if (cause.isInitiatedByApplication()) { + LOG.debug("Shutdown is initiated by application. Ignoring it."); + } else { + LOG.error("Shutdown is NOT initiated by application. Resetting the channel.", cause); /* We cannot re-initialize channel here directly because ShutdownListener callbacks run in the connection's thread, so the call to createChannel causes a deadlock since it blocks waiting for a response (whilst the connection's thread is stuck executing the listener). */ - closeChannel(channel); - channel = null; - } + + Channel oldChannel = channelReference.getAndSet(null); + closeChannel(oldChannel); } }); + + return newChannel; } private void closeChannel(Channel channel) { try { + LOG.debug("Closing channel. Channel number = [{}]", channel.getChannelNumber()); channel.abort(); + LOG.debug("Channel [{}] closed.", channel.getChannelNumber()); } catch (IOException e) { LOG.info("Error closing AMQP channel", e.getCause()); } diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java index f1a27b2d..f1ab7657 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java @@ -29,7 +29,7 @@ public AmqpProducerAdapter(String topic, ExchangeType exchangeType, AmqpBrokerCo try { amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, exchangeType.value(), false /* durable */, true /* auto-delete */, null); - } catch (IOException e) { + } catch (Exception e) { throw new ChannelException("Failed to setup channel from ActiveMQ connection", e); } } @@ -49,7 +49,7 @@ public void publish(String jsonMessage, String routingKey) { try { amqpAutoRecoveringChannel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_BASIC, jsonMessage.getBytes(charset)); - } catch (IOException e) { + } catch (Exception e) { throw new ChannelException(String.format("Failed to publish message '%s' into exchange '%s' with routing key '%s'", jsonMessage, exchangeName, routingKey), e); } } diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannelTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannelTest.java new file mode 100644 index 00000000..fc37d5d6 --- /dev/null +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannelTest.java @@ -0,0 +1,57 @@ +package io.github.tcdl.msb.adapters.amqp; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Collections; + +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class AmqpAutoRecoveringChannelTest { + + @Mock + AmqpConnectionManager connectionManager; + @Mock + Connection connection; + @Mock + Channel firstChannel, secondChannel; + + AmqpAutoRecoveringChannel autoRecoveringChannel; + + @Before + public void setUp() throws Exception { + when(connection.createChannel()).thenReturn(firstChannel); + when(connectionManager.obtainConnection()).thenReturn(connection); + + autoRecoveringChannel = new AmqpAutoRecoveringChannel(connectionManager); + } + + @Test + public void testNewChannelOpened() throws Exception { + autoRecoveringChannel.exchangeDeclare("test-exchange", "test-exchange-type", false, false, Collections.emptyMap()); + verify(connection).createChannel(); + } + + @Test + public void testClosedChannel_replaced() throws Exception { + + Channel secondChannel = mock(Channel.class); + + when(connection.createChannel()).thenReturn(firstChannel, secondChannel); + + autoRecoveringChannel.exchangeDeclare("test-exchange", "test-exchange-type", false, false, Collections.emptyMap()); + when(firstChannel.isOpen()).thenReturn(false); + autoRecoveringChannel.exchangeDeclare("test-exchange", "test-exchange-type", false, false, Collections.emptyMap()); + + verify(connection, times(2)).createChannel(); + verify(firstChannel).abort(); + verify(firstChannel).exchangeDeclare(anyString(), anyString(), anyBoolean(), anyBoolean(), anyMapOf(String.class, Object.class)); + verify(secondChannel).exchangeDeclare(anyString(), anyString(), anyBoolean(), anyBoolean(), anyMapOf(String.class, Object.class)); + } +} \ No newline at end of file From ecd5de4c975637a3f3f95468ea16ffbbafa8f912 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 24 Oct 2016 13:25:27 +0300 Subject: [PATCH 158/226] Updated release notes --- release-notes.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/release-notes.html b/release-notes.html index 29a6acd4..642e9a9c 100644 --- a/release-notes.html +++ b/release-notes.html @@ -9,8 +9,14 @@

Welcome to MSB-Java version 1.5.2

October 3, 2016

+Changes in MSB-Java version 1.6.0 (not yet released)
+   - Broken backward compatibility with 1.5.2 version. Now exchange type for AMQ is not determined by routing key
+    presence/absence. It can be set explicitly using AmqpRequestOptions. Default value 'fanout' is used if exchange
+    type is not specified. ResponderServer mirrors this behavior using AmqpResponderOptions (see examples for routing key)
+   - Fixed possible deadlock in AMQP channel automatic recovery logic.
+
 Features of MSB-Java version 1.5.2:
-   - Added routing key support to API and AMQP adapters
+   - Added routing key support to API and AMQP adapter
    - Added "routingKey" field to message envelope "topics" section
    - Removed confusing response topic for cases when no responses/acks are expected
 

From 853dc09c5b0383c4d22f8b29f4d57cd1927d3258 Mon Sep 17 00:00:00 2001
From: alex 
Date: Mon, 24 Oct 2016 14:27:57 +0300
Subject: [PATCH 159/226] MSB.md updated

---
 doc/MSB.md | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/doc/MSB.md b/doc/MSB.md
index aeb1e200..c0be5835 100644
--- a/doc/MSB.md
+++ b/doc/MSB.md
@@ -214,11 +214,7 @@ public class PingService {
 ```java
 package io.github.tcdl.msb.examples;
 
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.MsbContext;
-import io.github.tcdl.msb.api.MsbContextBuilder;
-import io.github.tcdl.msb.api.ObjectFactory;
-import io.github.tcdl.msb.api.ResponderServer;
+import io.github.tcdl.msb.api.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -232,11 +228,13 @@ public class PongService {
 
         ObjectFactory objectFactory = msbContext.getObjectFactory();
         MessageTemplate messageTemplate = new MessageTemplate().withTags("pong-static-tag");
-        ResponderServer responderServer = objectFactory.createResponderServer("pingpong:namespace", messageTemplate, (request, responder) -> {
+        ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build();
+        ResponderServer responderServer = objectFactory.createResponderServer("pingpong:namespace", responderOptions,
+                (request, responderContext) -> {
             // Response handling logic
             LOG.info(String.format("Handling %s...", request));
 
-            responder.send("PONG");
+            responderContext.getResponder().send("PONG");
 
             LOG.info("Response sent");
         }, String.class);

From c5c70b56a55d392d62dc66d0a21261944b728e86 Mon Sep 17 00:00:00 2001
From: Sergey Ivanov 
Date: Tue, 1 Nov 2016 13:05:04 +0200
Subject: [PATCH 160/226] Autoconfigure for spring boot

Autoconfigure for spring boot
---
 acceptance/pom.xml                            |   2 +-
 .../tcdl/msb/acceptance/MsbTestHelper.java    |  10 +-
 .../MultipleRequesterResponder.java           |   5 +-
 .../msb/acceptance/MultipleResponder.java     |   8 +-
 .../bdd/steps/RequesterResponderSteps.java    |  47 +++--
 .../scenarios/blocking_requester.story        |   1 -
 .../resources/scenarios/routing_keys.story    |  18 +-
 amqp/pom.xml                                  |   2 +-
 .../msb/adapters/amqp/AmqpAdapterFactory.java |  57 ++++--
 .../adapters/amqp/AmqpConsumerAdapter.java    |  49 ++---
 .../adapters/amqp/AmqpProducerAdapter.java    |  29 +--
 .../tcdl/msb/api/AmqpRequestOptions.java      |  48 +++++
 .../tcdl/msb/api/AmqpResponderOptions.java    |  55 ++++++
 .../io/github/tcdl/msb/api/ExchangeType.java  |  11 ++
 .../msb/config/amqp/AmqpBrokerConfig.java     |  12 +-
 amqp/src/main/resources/amqp.conf             |   4 +
 .../adapters/amqp/AmqpAdapterFactoryTest.java |  51 +++--
 .../amqp/AmqpConsumerAdapterTest.java         |  35 ++--
 .../amqp/AmqpProducerAdapterTest.java         |  17 +-
 .../api/AmqpResponderOptionsBuilderTest.java  |  16 ++
 .../msb/config/amqp/AmqpBrokerConfigTest.java |   6 +
 cli/pom.xml                                   |   2 +-
 core/pom.xml                                  |   2 +-
 .../io/github/tcdl/msb/ChannelManager.java    | 156 ++++-----------
 .../java/io/github/tcdl/msb/Consumer.java     |   9 +-
 .../tcdl/msb/adapters/AdapterFactory.java     |  27 ++-
 .../tcdl/msb/api/MessageDestination.java      |  51 -----
 .../io/github/tcdl/msb/api/ObjectFactory.java | 179 ++++++++----------
 .../github/tcdl/msb/api/RequestOptions.java   |  22 ++-
 .../github/tcdl/msb/api/ResponderOptions.java |  60 ++++++
 .../exception/AdapterCreationException.java   |  11 ++
 .../github/tcdl/msb/collector/Collector.java  |  10 +-
 .../tcdl/msb/impl/ObjectFactoryImpl.java      | 102 ++--------
 .../github/tcdl/msb/impl/RequesterImpl.java   |   8 +-
 .../github/tcdl/msb/impl/ResponderImpl.java   |   3 +-
 .../tcdl/msb/impl/ResponderServerImpl.java    |  23 +--
 .../agent/DefaultChannelMonitorAgent.java     |   3 +-
 .../msb/ChannelManagerConcurrentTest.java     |   9 +-
 .../github/tcdl/msb/ChannelManagerTest.java   | 109 ++++-------
 .../java/io/github/tcdl/msb/ConsumerTest.java |   7 -
 .../tcdl/msb/api/RequesterResponderIT.java    |   8 +-
 .../tcdl/msb/collector/CollectorTest.java     |   6 +-
 .../tcdl/msb/impl/RequesterImplTest.java      | 104 ++--------
 .../tcdl/msb/impl/ResponderImplTest.java      |   6 +-
 .../msb/impl/ResponderServerImplTest.java     |  61 +++---
 .../adapterfactory/TestMsbAdapterFactory.java |  26 +--
 .../objectfactory/TestMsbObjectFactory.java   | 105 +---------
 .../agent/DefaultChannelMonitorAgentTest.java |  17 +-
 examples/pom.xml                              |   2 +-
 .../msb/examples/ConsumerWithRoutingKeys.java |  12 +-
 .../tcdl/msb/examples/DateExtractor.java      |   9 +-
 .../tcdl/msb/examples/FacetsAggregator.java   |  12 +-
 .../github/tcdl/msb/examples/PongService.java |   3 +-
 .../msb/examples/ProducerWithRoutingKey.java  |  10 +-
 jmeter/pom.xml                                |   6 +-
 .../main/resources/META-INF/spring.factories  |   2 -
 .../src/main/resources/defaultMsbConfig.conf  |  35 ----
 pom.xml                                       |   4 +-
 release-notes.html                            |   4 +-
 .../pom.xml                                   |  60 +++---
 .../MsbConfigAutoConfiguration.java           |  60 ++++--
 .../MsbContextAutoConfiguration.java          |   2 +-
 .../msb/autoconfigure}/MsbProperties.java     | 141 ++++++++++++--
 .../main/resources/META-INF/spring.factories  |   2 +
 .../MsbAutoConfigurationTest.java             | 101 ++++++++++
 65 files changed, 1044 insertions(+), 1030 deletions(-)
 create mode 100644 amqp/src/main/java/io/github/tcdl/msb/api/AmqpRequestOptions.java
 create mode 100644 amqp/src/main/java/io/github/tcdl/msb/api/AmqpResponderOptions.java
 create mode 100644 amqp/src/main/java/io/github/tcdl/msb/api/ExchangeType.java
 create mode 100644 amqp/src/test/java/io/github/tcdl/msb/api/AmqpResponderOptionsBuilderTest.java
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/api/ResponderOptions.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/api/exception/AdapterCreationException.java
 delete mode 100644 msb-spring-boot-starter/src/main/resources/META-INF/spring.factories
 delete mode 100644 msb-spring-boot-starter/src/main/resources/defaultMsbConfig.conf
 rename {msb-spring-boot-starter => spring-boot-starter}/pom.xml (58%)
 rename {msb-spring-boot-starter/src/main/java/io/github/tcdl/msb => spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure}/MsbConfigAutoConfiguration.java (51%)
 rename {msb-spring-boot-starter/src/main/java/io/github/tcdl/msb => spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure}/MsbContextAutoConfiguration.java (96%)
 rename {msb-spring-boot-starter/src/main/java/io/github/tcdl/msb => spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure}/MsbProperties.java (58%)
 create mode 100644 spring-boot-starter/src/main/resources/META-INF/spring.factories
 create mode 100644 spring-boot-starter/src/test/java/io/github/tcdl/msb/autoconfigure/MsbAutoConfigurationTest.java

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index ea2b14c6..3e4dfe74 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2-SNAPSHOT
+        1.5.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
index 2f1db527..2fd9258d 100644
--- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
+++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
@@ -139,20 +139,14 @@ public  CompletableFuture sendForResult(Requester requester, Object pay
         return requester.request(payload, tags);
     }
 
-    public ResponderServer createResponderServer(String namespace, ResponderServer.RequestHandler requestHandler) {
-        return createResponderServer(DEFAULT_CONTEXT_NAME, namespace, requestHandler);
-    }
-
     public ResponderServer createResponderServer(String contextName, String namespace, ResponderServer.RequestHandler requestHandler) {
-        MessageTemplate options = new MessageTemplate();
         System.out.println(">>> RESPONDER SERVER on: " + namespace);
-        return getContext(contextName).getObjectFactory().createResponderServer(namespace, options, requestHandler, RestPayload.class);
+        return getContext(contextName).getObjectFactory().createResponderServer(namespace, ResponderOptions.DEFAULTS, requestHandler, RestPayload.class);
     }
 
     public  ResponderServer createResponderServer(String namespace, ResponderServer.RequestHandler requestHandler, Class payloadClass) {
-        MessageTemplate options = new MessageTemplate();
         System.out.println(">>> RESPONDER SERVER on: " + namespace);
-        return getDefaultContext().getObjectFactory().createResponderServer(namespace, options, requestHandler, payloadClass);
+        return getDefaultContext().getObjectFactory().createResponderServer(namespace, ResponderOptions.DEFAULTS, requestHandler, payloadClass);
     }
 
     public void shutdown(String contextName) {
diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java
index 9fc799c0..ba43b4df 100644
--- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java
+++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleRequesterResponder.java
@@ -1,5 +1,6 @@
 package io.github.tcdl.msb.acceptance;
 
+import com.google.common.base.Joiner;
 import io.github.tcdl.msb.api.Requester;
 
 import java.util.List;
@@ -7,7 +8,6 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 
 public class MultipleRequesterResponder {
 
@@ -32,7 +32,8 @@ public void runMultipleRequesterResponder() {
         util.createResponderServer(responderNamespace, (request, responderContext) -> {
             System.out.print(">>> REQUEST: " + request);
             Runnable onFinalResponse = () -> {
-                responderContext.getResponder().send("response from MultipleRequesterResponder:" + StringUtils.join(responseBodies));
+                String responses = Joiner.on(StringUtils.EMPTY).join(responseBodies);
+                responderContext.getResponder().send("response from MultipleRequesterResponder:" + responses);
             };
             createAndRunRequester(requesterNamespace1, onFinalResponse);
             createAndRunRequester(requesterNamespace2, onFinalResponse);
diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java
index 9a632e3e..6d405798 100644
--- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java
+++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MultipleResponder.java
@@ -1,14 +1,13 @@
 package io.github.tcdl.msb.acceptance;
 
-import io.github.tcdl.msb.api.MessageTemplate;
+import com.fasterxml.jackson.core.type.TypeReference;
 import io.github.tcdl.msb.api.MsbContext;
 import io.github.tcdl.msb.api.MsbContextBuilder;
+import io.github.tcdl.msb.api.ResponderOptions;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 
 import java.util.Map;
 
-import com.fasterxml.jackson.core.type.TypeReference;
-
 public class MultipleResponder {
 
     public static void main(String... args) {
@@ -19,8 +18,7 @@ public static void main(String... args) {
     }
 
     public static void runResponder(String namespace, MsbContext msbContext) {
-        MessageTemplate options = new MessageTemplate();
-        msbContext.getObjectFactory().createResponderServer(namespace, options, (request, responderContext) -> {
+        msbContext.getObjectFactory().createResponderServer(namespace, ResponderOptions.DEFAULTS, (request, responderContext) -> {
             Map requestBody = request.getBody();
             System.out.println(">>> GOT request: " + requestBody);
 
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 4885318a..563de7e0 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -4,10 +4,7 @@
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigValueFactory;
-import io.github.tcdl.msb.api.MessageDestination;
-import io.github.tcdl.msb.api.MessageTemplate;
-import io.github.tcdl.msb.api.MsbContext;
-import io.github.tcdl.msb.api.Requester;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.Topics;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
@@ -15,6 +12,7 @@
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.hamcrest.Matchers;
+import org.jbehave.core.annotations.BeforeScenario;
 import org.jbehave.core.annotations.Given;
 import org.jbehave.core.annotations.Then;
 import org.jbehave.core.annotations.When;
@@ -28,9 +26,7 @@
 import java.util.stream.Collectors;
 
 import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 
 /**
  * Steps to send requests and respond with predefined responses
@@ -254,7 +250,7 @@ public void sendRequestWithBody(String body) throws Exception {
         helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse, this::onEnd);
     }
 
-    @Given("$responderId responder server listens on namespace $namespace with routing keys $routingKeys")
+    @Given("$responderId responder server listens on namespace $namespace with binding keys $routingKeys")
     public void subscribeResponder(String responderId, String namespace, List routingKeys) {
         //modify name to make library generate different queue names for different consumers (responders)
         Config config = ConfigFactory.load()
@@ -262,7 +258,13 @@ public void subscribeResponder(String responderId, String namespace, List(routingKeys), new MessageTemplate(),
+
+        ResponderOptions amqpResponderOptions = new AmqpResponderOptions.Builder()
+                .withBindingKeys(new HashSet<>(routingKeys))
+                .withExchangeType(ExchangeType.TOPIC)
+                .build();
+
+        context.getObjectFactory().createResponderServer(namespace, amqpResponderOptions,
                 (request, responderContext) -> {
                     receivedMessagesByConsumer.computeIfAbsent(responderId, key -> new LinkedList<>()).add(request);
                 }, String.class).listen();
@@ -379,8 +381,8 @@ public void checkTopicsSection(ExamplesTable table) throws InterruptedException
         assertEquals("Invalid value of field 'routingKey'", expectedRoutingKey, topics.getRoutingKey());
     }
 
-    private String normalizeNull(String string){
-        if(string !=null && string.trim().equalsIgnoreCase("null")){
+    private String normalizeNull(String string) {
+        if (string != null && string.trim().equalsIgnoreCase("null")) {
             return null;
         } else {
             return string;
@@ -421,16 +423,26 @@ public void requestForSingleResult(String namespace) throws Exception {
     @When("requester sends to $namespace a request with body '$body' and routing key $routingKey")
     public void requestForSingleResult(String namespace, String body, String routingKey) throws Exception {
         helper.initDefault();
+        RequestOptions requestOptions = new AmqpRequestOptions.Builder()
+                .withExchangeType(ExchangeType.TOPIC)
+                .withRoutingKey(routingKey).build();
+
         helper.getContext(DEFAULT_CONTEXT_NAME).getObjectFactory()
-                .createRequesterForFireAndForget(new MessageDestination(namespace, routingKey), new MessageTemplate())
+                .createRequesterForFireAndForget(namespace, requestOptions)
                 .publish(body);
     }
 
     @When("requester sends to $namespace a request with forward namespace $forwardNamespace, body '$body' and routing key $routingKey")
     public void publishWithRoutingKey(String namespace, String forwardNamespace, String body, String routingKey) throws Exception {
         helper.initDefault();
+
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withForwardNamespace(forwardNamespace)
+                .withRoutingKey(routingKey)
+                .build();
+
         helper.getContext(DEFAULT_CONTEXT_NAME).getObjectFactory()
-                .createRequesterForFireAndForget(namespace, new MessageDestination(forwardNamespace, routingKey), new MessageTemplate())
+                .createRequesterForFireAndForget(namespace, requestOptions)
                 .publish(body);
     }
 
@@ -480,8 +492,13 @@ public void assertExceptionOccured() throws Exception {
         fail("Expected exception not thrown");
     }
 
-    @Then("reset mock responses")
-    public void  resetMockResponses(){
+//    @Then("reset mock responses")
+//    public void resetMockResponses() {
+//        responses.clear();
+//    }
+
+    @BeforeScenario
+    public void resetMockResponses() {
         responses.clear();
     }
 }
diff --git a/acceptance/src/test/resources/scenarios/blocking_requester.story b/acceptance/src/test/resources/scenarios/blocking_requester.story
index a2bf434b..d8c41843 100644
--- a/acceptance/src/test/resources/scenarios/blocking_requester.story
+++ b/acceptance/src/test/resources/scenarios/blocking_requester.story
@@ -3,7 +3,6 @@ Before:
 Given MSB configuration with consumer thread pool size 1
 And start MSB
 And clear log
-And reset mock responses
 After:
 Outcome: ANY
 Then shutdown MSB
diff --git a/acceptance/src/test/resources/scenarios/routing_keys.story b/acceptance/src/test/resources/scenarios/routing_keys.story
index 31f2f136..a24f1639 100644
--- a/acceptance/src/test/resources/scenarios/routing_keys.story
+++ b/acceptance/src/test/resources/scenarios/routing_keys.story
@@ -6,22 +6,10 @@ Outcome: ANY
 Then shutdown MSB
 
 Scenario: consumers receive messages according to routing keys
-Given 1st responder server listens on namespace test:namespace with routing keys routing-key-1, routing-key-2
-Given 2nd responder server listens on namespace test:namespace with routing keys routing-key-3
+Given 1st responder server listens on namespace test:namespace with binding keys routing-key-1, routing-key-2
+Given 2nd responder server listens on namespace test:namespace with binding keys routing-key-3
 When requester sends to test:namespace a request with body '{"messageId":"rk1"}' and routing key routing-key-1
 When requester sends to test:namespace a request with body '{"messageId":"rk2"}' and routing key routing-key-2
 When requester sends to test:namespace a request with body '{"messageId":"rk3"}' and routing key routing-key-3
 Then 1st responder receives only messages '{"messageId":"rk1"}', '{"messageId":"rk2"}'
-Then 2nd responder receives only messages '{"messageId":"rk3"}'
-
-Scenario: routing key is ignored for messages that require forwarding
-Given responder server listens on fanout namespace test:routing
-And requester sets forwarding to test:routing:forwarding and target namespace to test:routing
-When requester sends to test:routing a request with forward namespace test:routing:forwarding, body '{"messageId":"rk1"}' and routing key routing-key-1
-Then message envelope topics section is
-|to          |forward                |response|routingKey   |
-|test:routing|test:routing:forwarding|null    |routing-key-1|
-When requester sends to test:routing a request with forward namespace test:routing:forwarding, body '{"messageId":"rk2"}' without routing key
-Then message envelope topics section is
-|to          |forward                |response|routingKey|
-|test:routing|test:routing:forwarding|null    |          |
\ No newline at end of file
+Then 2nd responder receives only messages '{"messageId":"rk3"}'
\ No newline at end of file
diff --git a/amqp/pom.xml b/amqp/pom.xml
index a96f129e..564e365a 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2-SNAPSHOT
+        1.5.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
index b9b62c74..6c931375 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
@@ -7,27 +7,26 @@
 import com.typesafe.config.ConfigFactory;
 import io.github.tcdl.msb.adapters.AdapterFactory;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
-import io.github.tcdl.msb.adapters.ProducerAdapter;
-import io.github.tcdl.msb.api.MessageDestination;
+import io.github.tcdl.msb.api.*;
+import io.github.tcdl.msb.api.exception.AdapterCreationException;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
+import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
 import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.*;
+import java.util.concurrent.TimeoutException;
 
 /**
  * AmqpAdapterFactory is an implementation of {@link AdapterFactory}
  * for {@link AmqpProducerAdapter} and {@link AmqpConsumerAdapter}
  */
 public class AmqpAdapterFactory implements AdapterFactory {
+
     private static final Logger LOG = LoggerFactory.getLogger(AmqpAdapterFactory.class);
 
     private volatile AmqpBrokerConfig amqpBrokerConfig;
@@ -45,6 +44,7 @@ public void init(MsbConfig msbConfig) {
         connectionManager = createConnectionManager(connection);
     }
 
+    //TODO extract config loading from this class and then rewrite unit test for this class completely
     protected AmqpBrokerConfig createAmqpBrokerConfig(MsbConfig msbConfig) {
         Config amqpApplicationConfig = msbConfig.getBrokerConfig();
         Config amqpLibConfig = ConfigFactory.load("amqp").getConfig("config.amqp");
@@ -61,23 +61,48 @@ protected AmqpBrokerConfig createAmqpBrokerConfig(MsbConfig msbConfig) {
     }
     
     @Override
-    public ProducerAdapter createProducerAdapter(String topic) {
-        return new AmqpProducerAdapter(topic, amqpBrokerConfig, connectionManager);
-    }
+    public AmqpProducerAdapter createProducerAdapter(String topic, RequestOptions requestOptions) {
+        Validate.notNull(topic, "topic is mandatory");
+        Validate.notNull(requestOptions, "requestOptions are mandatory");
+
+        Class requestOptionsClass = requestOptions.getClass();
+        ExchangeType exchangeType;
+
+        if (AmqpRequestOptions.class.isAssignableFrom(requestOptionsClass)) {
+            exchangeType = ((AmqpRequestOptions) requestOptions).getExchangeType();
+        } else if (requestOptionsClass.equals(RequestOptions.class)) {
+            exchangeType = amqpBrokerConfig.getDefaultExchangeType();
+        } else {
+            throw new AdapterCreationException("Illegal for this AdapterFactory RequestOptions subclass");
+        }
 
-    @Override
-    public ProducerAdapter createProducerAdapter(MessageDestination destination) {
-        return new AmqpProducerAdapter(destination, amqpBrokerConfig, connectionManager);
+        return new AmqpProducerAdapter(topic, exchangeType, amqpBrokerConfig, connectionManager);
     }
 
     @Override
-    public ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
-        return new AmqpConsumerAdapter(topic, amqpBrokerConfig, connectionManager, isResponseTopic);
+    public AmqpConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
+        return new AmqpConsumerAdapter(topic, amqpBrokerConfig.getDefaultExchangeType(),
+                ResponderOptions.DEFAULTS.getBindingKeys(),
+                amqpBrokerConfig, connectionManager, isResponseTopic);
     }
 
     @Override
-    public ConsumerAdapter createConsumerAdapter(String topic, Set routingKeys) {
-        return new AmqpConsumerAdapter(topic, routingKeys, amqpBrokerConfig, connectionManager);
+    public AmqpConsumerAdapter createConsumerAdapter(String topic, ResponderOptions responderOptions, boolean isResponseTopic) {
+        Validate.notEmpty(topic, "topic is mandatory");
+        Validate.notNull(responderOptions, "responderOptions are mandatory");
+
+        Class responderOptionsClass = responderOptions.getClass();
+        ExchangeType exchangeType;
+
+        if (AmqpResponderOptions.class.isAssignableFrom(responderOptionsClass)) {
+            exchangeType = ((AmqpResponderOptions) responderOptions).getExchangeType();
+        } else if (responderOptionsClass.equals(ResponderOptions.class)) {
+            exchangeType = amqpBrokerConfig.getDefaultExchangeType();
+        } else {
+            throw new AdapterCreationException("Illegal for this AdapterFactory ResponderOptions subclass");
+        }
+
+        return new AmqpConsumerAdapter(topic, exchangeType, responderOptions.getBindingKeys(), amqpBrokerConfig, connectionManager, isResponseTopic);
     }
 
     protected ConnectionFactory createConnectionFactory(AmqpBrokerConfig adapterConfig) {
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
index 0a0788e0..4c44ecd9 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
@@ -1,60 +1,41 @@
 package io.github.tcdl.msb.adapters.amqp;
 
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-import com.google.common.collect.Lists;
-import com.rabbitmq.client.AMQP;
 import com.rabbitmq.client.Channel;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
-import io.github.tcdl.msb.api.MessageDestination;
+import io.github.tcdl.msb.api.ExchangeType;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import io.github.tcdl.msb.support.Utils;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Set;
+
 
 public class AmqpConsumerAdapter implements ConsumerAdapter {
 
     private Channel channel;
     private final String exchangeName;
-    private final Set routingKeys;
+    private final Set bindingKeys;
     private String consumerTag;
     private AmqpBrokerConfig adapterConfig;
     private boolean isResponseTopic = false;
 
-    /**
-     * The constructor.
-     *
-     * @param exchangeName - an exchange name associated with the adapter
-     * @throws ChannelException if adapter failed to create channel and declare exchange
-     */
-    public AmqpConsumerAdapter(String exchangeName, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager, boolean isResponseTopic) {
-        this(exchangeName, "fanout", Collections.singleton(StringUtils.EMPTY), amqpBrokerConfig, connectionManager, isResponseTopic);
-    }
-
-    public AmqpConsumerAdapter(String topic, Set routingKeys, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
-        this(topic, "topic", routingKeys, amqpBrokerConfig, connectionManager, false);
-    }
-
-    private AmqpConsumerAdapter(String exchangeName, String exchangeType, Set routingKeys, AmqpBrokerConfig amqpBrokerConfig,
-                                AmqpConnectionManager connectionManager, boolean isResponseTopic) {
+    public AmqpConsumerAdapter(String exchangeName, ExchangeType exchangeType, Set bindingKeys, AmqpBrokerConfig amqpBrokerConfig,
+                               AmqpConnectionManager connectionManager, boolean isResponseTopic) {
 
         Validate.notNull(exchangeName, "Exchange name is required");
-        Validate.notEmpty(routingKeys, "At least one routing key is required");
+        Validate.notNull(exchangeType, "Exchange type is required");
+        Validate.notEmpty(bindingKeys, "At least one routing key is required");
 
-        this.routingKeys = routingKeys;
+        this.bindingKeys = bindingKeys;
         this.exchangeName = exchangeName;
         this.adapterConfig = amqpBrokerConfig;
         this.isResponseTopic = isResponseTopic;
 
         try {
             channel = connectionManager.obtainConnection().createChannel();
-            channel.exchangeDeclare(exchangeName, exchangeType, false /* durable */, true /* auto-delete */, null);
+            channel.exchangeDeclare(exchangeName, exchangeType.value(), false /* durable */, true /* auto-delete */, null);
         } catch (IOException e) {
             throw new ChannelException("Failed to setup channel", e);
         }
@@ -74,12 +55,12 @@ public void subscribe(RawMessageHandler msgHandler) {
         try {
             channel.queueDeclare(queueName, durable /* durable */, false /* exclusive */, !durable /*auto-delete */, null);
             channel.basicQos(prefetchCount); // Don't accept more messages if we have any unacknowledged
-            for(String routingKey: routingKeys) {
-                channel.queueBind(queueName, exchangeName, routingKey);
+            for(String bindingKey : bindingKeys) {
+                channel.queueBind(queueName, exchangeName, bindingKey);
             }
             consumerTag = channel.basicConsume(queueName, false /* autoAck */, new AmqpMessageConsumer(channel, msgHandler, adapterConfig));
         } catch (IOException e) {
-            throw new ChannelException(String.format("Failed to subscribe to topic %s with routing keys %s", exchangeName, routingKeys), e);
+            throw new ChannelException(String.format("Failed to subscribe to topic %s with routing keys %s", exchangeName, bindingKeys), e);
         }
     }
 
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
index 39909b24..f1a27b2d 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
@@ -2,7 +2,7 @@
 
 import com.rabbitmq.client.MessageProperties;
 import io.github.tcdl.msb.adapters.ProducerAdapter;
-import io.github.tcdl.msb.api.MessageDestination;
+import io.github.tcdl.msb.api.ExchangeType;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import org.apache.commons.lang3.StringUtils;
@@ -12,32 +12,23 @@
 import java.nio.charset.Charset;
 
 public class AmqpProducerAdapter implements ProducerAdapter {
-    private String exchangeName;
-    private AmqpBrokerConfig amqpBrokerConfig;
-    private AmqpAutoRecoveringChannel amqpAutoRecoveringChannel;
 
-    /**
-     * The constructor.
-     * @param topic - a topic name associated with the adapter
-     * @throws ChannelException if some problems during setup channel from RabbitMQ connection were occurred
-     */
-    public AmqpProducerAdapter(String topic, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
-        this(topic, "fanout", amqpBrokerConfig, connectionManager);
-    }
-
-    public AmqpProducerAdapter(MessageDestination destination, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
-        this(destination.getTopic(), "topic", amqpBrokerConfig, connectionManager);
-    }
+    final String exchangeName;
+    final AmqpBrokerConfig amqpBrokerConfig;
+    final AmqpAutoRecoveringChannel amqpAutoRecoveringChannel;
 
-    public AmqpProducerAdapter(String topic, String exchangeType, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
-        Validate.notNull(topic, "the 'topic' must not be null");
+    public AmqpProducerAdapter(String topic, ExchangeType exchangeType, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
+        Validate.notNull(topic, "Topic is mandatory");
+        Validate.notNull(exchangeType, "Exchange type is mandatory");
+        Validate.notNull(amqpBrokerConfig, "Broker config is mandatory");
+        Validate.notNull(exchangeType, "Connection manager is mandatory");
 
         this.exchangeName = topic;
         this.amqpBrokerConfig = amqpBrokerConfig;
         this.amqpAutoRecoveringChannel = new AmqpAutoRecoveringChannel(connectionManager);
 
         try {
-            amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, exchangeType, false /* durable */, true /* auto-delete */, null);
+            amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, exchangeType.value(), false /* durable */, true /* auto-delete */, null);
         } catch (IOException e) {
             throw new ChannelException("Failed to setup channel from ActiveMQ connection", e);
         }
diff --git a/amqp/src/main/java/io/github/tcdl/msb/api/AmqpRequestOptions.java b/amqp/src/main/java/io/github/tcdl/msb/api/AmqpRequestOptions.java
new file mode 100644
index 00000000..9fe5830f
--- /dev/null
+++ b/amqp/src/main/java/io/github/tcdl/msb/api/AmqpRequestOptions.java
@@ -0,0 +1,48 @@
+package io.github.tcdl.msb.api;
+
+import org.apache.commons.lang3.Validate;
+
+import javax.annotation.Nonnull;
+
+
+public class AmqpRequestOptions extends RequestOptions {
+
+    private final ExchangeType exchangeType;
+
+    private AmqpRequestOptions(Integer ackTimeout,
+                               Integer responseTimeout,
+                               Integer waitForResponses,
+                               MessageTemplate messageTemplate,
+                               String forwardNamespace,
+                               String routingKey,
+                               ExchangeType exchangeType) {
+
+        super(ackTimeout, responseTimeout, waitForResponses, messageTemplate, forwardNamespace, routingKey);
+        this.exchangeType = exchangeType;
+    }
+
+    public ExchangeType getExchangeType() {
+        return exchangeType;
+    }
+
+    @Override
+    public RequestOptions.Builder asBuilder() {
+        return ((AmqpRequestOptions.Builder) (new Builder().from(this))).withExchangeType(this.exchangeType);
+    }
+
+    public static class Builder extends RequestOptions.Builder {
+
+        private ExchangeType exchangeType;
+
+        public Builder withExchangeType(ExchangeType exchangeType){
+            this.exchangeType = exchangeType;
+            return this;
+        }
+
+        @Override
+        public RequestOptions build() {
+            return new AmqpRequestOptions(ackTimeout, responseTimeout, waitForResponses, messageTemplate,
+                    forwardNamespace, routingKey, exchangeType);
+        }
+    }
+}
diff --git a/amqp/src/main/java/io/github/tcdl/msb/api/AmqpResponderOptions.java b/amqp/src/main/java/io/github/tcdl/msb/api/AmqpResponderOptions.java
new file mode 100644
index 00000000..ced0c117
--- /dev/null
+++ b/amqp/src/main/java/io/github/tcdl/msb/api/AmqpResponderOptions.java
@@ -0,0 +1,55 @@
+package io.github.tcdl.msb.api;
+
+import org.apache.commons.lang3.Validate;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.Set;
+
+
+public class AmqpResponderOptions extends ResponderOptions{
+
+    public static final String MATCH_ALL_BINDING_KEY = "#";
+    private final ExchangeType exchangeType;
+
+    protected AmqpResponderOptions(Set bindingKeys,
+                                   MessageTemplate messageTemplate,
+                                   ExchangeType exchangeType) {
+        super(bindingKeys, messageTemplate);
+        this.exchangeType = exchangeType;
+    }
+
+    public ExchangeType getExchangeType() {
+        return exchangeType;
+    }
+
+    public static class Builder extends ResponderOptions.Builder {
+
+        private ExchangeType exchangeType;
+
+        public Builder withMessageTemplate(MessageTemplate responseMessageTemplate) {
+            this.messageTemplate = responseMessageTemplate;
+            return this;
+        }
+
+        public Builder withBindingKeys(Set bindingKeys) {
+            this.bindingKeys = bindingKeys;
+            return this;
+        }
+
+        public Builder withExchangeType(@Nonnull ExchangeType exchangeType){
+            Validate.notNull(exchangeType);
+            this.exchangeType = exchangeType;
+            return this;
+        }
+
+        @Override
+        public ResponderOptions build() {
+            Set bindingKeys = this.bindingKeys == null || this.bindingKeys.isEmpty()
+                    ? Collections.singleton(MATCH_ALL_BINDING_KEY)
+                    : this.bindingKeys;
+
+            return new AmqpResponderOptions(bindingKeys, messageTemplate, exchangeType);
+        }
+    }
+}
diff --git a/amqp/src/main/java/io/github/tcdl/msb/api/ExchangeType.java b/amqp/src/main/java/io/github/tcdl/msb/api/ExchangeType.java
new file mode 100644
index 00000000..6307de9a
--- /dev/null
+++ b/amqp/src/main/java/io/github/tcdl/msb/api/ExchangeType.java
@@ -0,0 +1,11 @@
+package io.github.tcdl.msb.api;
+
+public enum ExchangeType {
+
+    FANOUT,
+    TOPIC;
+
+    public String value(){
+        return this.name().toLowerCase();
+    }
+}
diff --git a/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java b/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java
index 3bd88fcb..9117c42d 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfig.java
@@ -1,5 +1,6 @@
 package io.github.tcdl.msb.config.amqp;
 
+import io.github.tcdl.msb.api.ExchangeType;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
 import io.github.tcdl.msb.config.ConfigurationUtil;
 
@@ -21,6 +22,7 @@ public class AmqpBrokerConfig {
 
     private Optional groupId;
     private final boolean durable;
+    private ExchangeType defaultExchangeType;
     private final int heartbeatIntervalSec;
     private final long networkRecoveryIntervalMs;
     private final int prefetchCount;
@@ -28,6 +30,7 @@ public class AmqpBrokerConfig {
     public AmqpBrokerConfig(Charset charset, String host, int port,
             Optional username, Optional password, Optional virtualHost, boolean useSSL,
             Optional groupId, boolean durable,
+            ExchangeType defaultExchangeType,
             int heartbeatIntervalSec, long networkRecoveryIntervalMs, int prefetchCount) {
         this.charset = charset;
         this.port = port;
@@ -38,6 +41,7 @@ public AmqpBrokerConfig(Charset charset, String host, int port,
         this.useSSL = useSSL;
         this.groupId = groupId;
         this.durable = durable;
+        this.defaultExchangeType = defaultExchangeType;
         this.heartbeatIntervalSec = heartbeatIntervalSec;
         this.networkRecoveryIntervalMs = networkRecoveryIntervalMs;
         this.prefetchCount = prefetchCount;
@@ -53,6 +57,7 @@ public static class AmqpBrokerConfigBuilder {
         private boolean useSSL;
         private Optional groupId;
         private boolean durable;
+        private ExchangeType defaultExchangeType;
         private int heartbeatIntervalSec;
         private long networkRecoveryIntervalMs;
         private int prefetchCount;
@@ -80,6 +85,7 @@ public AmqpBrokerConfigBuilder withConfig(Config config) {
 
             this.groupId = ConfigurationUtil.getOptionalString(config, "groupId");
             this.durable = ConfigurationUtil.getBoolean(config, "durable");
+            this.defaultExchangeType = ExchangeType.valueOf(ConfigurationUtil.getString(config, "defaultExchangeType").toUpperCase());
             this.heartbeatIntervalSec = ConfigurationUtil.getInt(config, "heartbeatIntervalSec");
             this.networkRecoveryIntervalMs = ConfigurationUtil.getLong(config, "networkRecoveryIntervalMs");
             this.prefetchCount = ConfigurationUtil.getInt(config, "prefetchCount");
@@ -91,7 +97,7 @@ public AmqpBrokerConfigBuilder withConfig(Config config) {
          */
         public AmqpBrokerConfig build() {
             return new AmqpBrokerConfig(charset, host, port, username, password, virtualHost, useSSL,
-                    groupId, durable,
+                    groupId, durable, defaultExchangeType,
                     heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount);
         }
     }
@@ -132,6 +138,10 @@ public boolean isDurable() {
         return durable;
     }
 
+    public ExchangeType getDefaultExchangeType() {
+        return defaultExchangeType;
+    }
+
     public void setGroupId(Optional groupId) {
         this.groupId = groupId;
     }
diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf
index af6ad658..725e2f4e 100644
--- a/amqp/src/main/resources/amqp.conf
+++ b/amqp/src/main/resources/amqp.conf
@@ -16,6 +16,10 @@ config.amqp = {
   #groupId = "msb-java"
   durable = false
 
+  # AMQP exchange type to be used if other is not specified by client code.
+  # Currently supported exchange types are "fanout" and "topic"
+  defaultExchangeType = "fanout"
+
   # Interval of the heartbeats that are used to detect broken connections. Zero for none. See for more details: https://www.rabbitmq.com/heartbeats.html
   heartbeatIntervalSec = 30
   # Interval of connection recovery attempts. See for more details: https://www.rabbitmq.com/api-guide.html#connection-recovery
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
index aa335fd5..473994cc 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactoryTest.java
@@ -1,32 +1,29 @@
 package io.github.tcdl.msb.adapters.amqp;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.verify;
-
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
-import io.github.tcdl.msb.adapters.ProducerAdapter;
+import io.github.tcdl.msb.api.ExchangeType;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
-
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.Optional;
-
 import org.junit.Before;
 import org.junit.Test;
-
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-import com.typesafe.config.Config;
-import com.typesafe.config.ConfigFactory;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
 @RunWith(MockitoJUnitRunner.class)
 public class AmqpAdapterFactoryTest {
 
@@ -57,37 +54,33 @@ public class AmqpAdapterFactoryTest {
     AmqpBrokerConfig amqpConfig;
     AmqpAdapterFactory amqpAdapterFactory;
     MsbConfig msbConfigurations;
-    
+
     @Before
     public void setUp() {
 
         msbConfigurations = new MsbConfig(CONFIG);
 
         amqpConfig = new AmqpBrokerConfig(charset, host, port,
-                Optional.of(username), Optional.of(password), Optional.of(virtualHost), useSSL, Optional.of(groupId), durable,
+                Optional.of(username), Optional.of(password), Optional.of(virtualHost), useSSL, Optional.of(groupId), durable, ExchangeType.FANOUT,
                 heartbeatIntervalSec, networkRecoveryIntervalMs, prefetchCount);
-        
+
         amqpAdapterFactory = new AmqpAdapterFactory() {
-            @Override
-            public ProducerAdapter createProducerAdapter(String topic) {
-                return new AmqpProducerAdapter(topic, amqpConfig, mockConnectionManager);
-            }
 
             @Override
-            public ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
-                return new AmqpConsumerAdapter(topic, amqpConfig, mockConnectionManager, isResponseTopic);
+            public AmqpConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic) {
+                return new AmqpConsumerAdapter(topic, ExchangeType.FANOUT, Collections.emptySet(), amqpConfig, mockConnectionManager, isResponseTopic);
             }
 
             @Override
             protected ConnectionFactory createConnectionFactory() {
                 return mockConnectionFactory;
             }
-            
+
             @Override
             protected AmqpBrokerConfig createAmqpBrokerConfig(MsbConfig msbConfig) {
                 return amqpConfig;
             }
-            
+
             @Override
             protected AmqpConnectionManager createConnectionManager(Connection connection) {
                 return mockConnectionManager;
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
index 013510b8..58e17e01 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
@@ -5,6 +5,8 @@
 import com.rabbitmq.client.Connection;
 import com.rabbitmq.client.Consumer;
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.api.ExchangeType;
+import io.github.tcdl.msb.api.ResponderOptions;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import org.apache.commons.collections.CollectionUtils;
@@ -36,7 +38,7 @@ public void setUp() throws Exception {
         Connection mockConnection = mock(Connection.class);
         mockChannel = mock(Channel.class);
         mockAmqpConnectionManager = mock(AmqpConnectionManager.class);
-        
+
         when(mockAmqpConnectionManager.obtainConnection()).thenReturn(mockConnection);
         when(mockConnection.createChannel()).thenReturn(mockChannel);
     }
@@ -57,9 +59,9 @@ public void testFanoutExchangeCreated() throws Exception {
     public void testTopicExchangeCreated() throws Exception {
         String topicName = "myTopic";
         String groupId = "groupId";
-        String routingKey = "routing-key";
+        String bindingKey = "binding-key";
 
-        new AmqpConsumerAdapter(topicName, Collections.singleton(routingKey), brokerConfig(groupId, true), mockAmqpConnectionManager);
+        new AmqpConsumerAdapter(topicName, ExchangeType.TOPIC, Collections.singleton(bindingKey), brokerConfig(groupId, true), mockAmqpConnectionManager, true);
         verify(mockChannel).exchangeDeclare(topicName, "topic", false, true, null);
 
     }
@@ -76,21 +78,22 @@ public void testSubscribeMultipleRoutingKeysMultipleBindings() throws Exception
         String topicName = "myTopic";
         String groupId = "groupId";
 
-        Set routingKeys = Sets.newHashSet("routing-key-1", "routing-key-2");
+        Set bindingKeys = Sets.newHashSet("routing-key-1", "routing-key-2");
 
-        AmqpConsumerAdapter amqpConsumerAdapter = new AmqpConsumerAdapter(topicName, routingKeys, brokerConfig(groupId, true), mockAmqpConnectionManager);
-        amqpConsumerAdapter.subscribe((jsonMessage, acknowledgementHandler) -> {});
+        AmqpConsumerAdapter amqpConsumerAdapter = new AmqpConsumerAdapter(topicName, ExchangeType.TOPIC, bindingKeys, brokerConfig(groupId, true), mockAmqpConnectionManager, false);
+        amqpConsumerAdapter.subscribe((jsonMessage, acknowledgementHandler) -> {
+        });
 
         ArgumentCaptor routingKeysCaptor = ArgumentCaptor.forClass(String.class);
         verify(mockChannel, times(2)).queueBind(eq("myTopic.groupId.d"), eq(topicName), routingKeysCaptor.capture());
 
-        assertTrue(CollectionUtils.isEqualCollection(routingKeys, routingKeysCaptor.getAllValues()));
+        assertTrue(CollectionUtils.isEqualCollection(bindingKeys, routingKeysCaptor.getAllValues()));
     }
 
     @Test
     public void testSubscribeTransientQueueCreated() throws IOException {
         AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf("myTopic", "myGroupId", false);
-        
+
         adapter.subscribe((jsonMessage, ackHandler) -> {
         });
 
@@ -144,7 +147,8 @@ public void testSubscribeException() throws IOException {
         AmqpConsumerAdapter adapter = createAdapterWithDurableConf("myTopic", "myGroupId", false);
         when(mockChannel.basicConsume(anyString(), anyBoolean(), any(AmqpMessageConsumer.class)))
                 .thenThrow(IOException.class);
-        adapter.subscribe((jsonMessage, ackHandler) -> {});
+        adapter.subscribe((jsonMessage, ackHandler) -> {
+        });
     }
 
     @Test
@@ -182,7 +186,8 @@ public void testUnsubscribeException() throws IOException {
         String consumerTag = "my consumer tag";
         when(mockChannel.basicConsume(anyString(), anyBoolean(), any(Consumer.class)))
                 .thenThrow(IOException.class);
-        adapter.subscribe((jsonMessage, ackHandler) -> {});
+        adapter.subscribe((jsonMessage, ackHandler) -> {
+        });
         adapter.unsubscribe();
     }
 
@@ -225,22 +230,22 @@ public void testIsDurableTrueIfNotResponseTopicAndDurableConfig() throws IOExcep
     private AmqpConsumerAdapter createAdapterWithNonDurableConf(String topic, String groupId, boolean isResponseTopic) {
         boolean isDurableConf = false;
         AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
-                false, Optional.of(groupId), isDurableConf, 1, 5000, 1);
-        return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
+                false, Optional.of(groupId), isDurableConf, ExchangeType.FANOUT, 1, 5000, 1);
+        return new AmqpConsumerAdapter(topic, ExchangeType.FANOUT, ResponderOptions.DEFAULTS.getBindingKeys(), nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
     }
 
     private AmqpConsumerAdapter createAdapterWithDurableConf(String topic, String groupId, boolean isResponseTopic) {
         boolean isDurableConf = true;
         AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
-                false, Optional.of(groupId), isDurableConf, 1, 5000, 1);
-        return new AmqpConsumerAdapter(topic, nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
+                false, Optional.of(groupId), isDurableConf, ExchangeType.FANOUT, 1, 5000, 1);
+        return new AmqpConsumerAdapter(topic, ExchangeType.FANOUT, ResponderOptions.DEFAULTS.getBindingKeys(), nondurableAmqpConfig, mockAmqpConnectionManager, isResponseTopic);
     }
 
     private AmqpBrokerConfig brokerConfig(String groupId, boolean durable) {
         return new AmqpBrokerConfig(
                 Charset.forName("UTF-8"),
                 "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
-                false, Optional.of(groupId), durable, 1, 5000, 1
+                false, Optional.of(groupId), durable, ExchangeType.FANOUT, 1, 5000, 1
         );
     }
 }
\ No newline at end of file
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java
index 4bdaa162..9bbb71fb 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java
@@ -4,6 +4,7 @@
 import com.rabbitmq.client.Channel;
 import com.rabbitmq.client.Connection;
 import com.rabbitmq.client.MessageProperties;
+import io.github.tcdl.msb.api.ExchangeType;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import org.apache.commons.lang3.StringUtils;
@@ -40,25 +41,27 @@ public void setUp() throws IOException {
     }
 
     @Test
-    public void testExchangeCreated() throws IOException {
+    public void testExchangeWithCorrectTypeCreated() throws IOException {
         String topicName = "myTopic";
 
-        new AmqpProducerAdapter(topicName, mockAmqpBrokerConfig, mockAmqpConnectionManager);
-
+        new AmqpProducerAdapter(topicName, ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager);
         verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null);
+
+        new AmqpProducerAdapter(topicName, ExchangeType.TOPIC, mockAmqpBrokerConfig, mockAmqpConnectionManager);
+        verify(mockChannel).exchangeDeclare(topicName, "topic", false, true, null);
     }
 
     @Test(expected = RuntimeException.class)
     public void testInitializationError() throws IOException {
         when(mockChannel.exchangeDeclare(anyString(), anyString(), anyBoolean(), anyBoolean(), any())).thenThrow(new IOException());
-        new AmqpProducerAdapter("myTopic", mockAmqpBrokerConfig, mockAmqpConnectionManager);
+        new AmqpProducerAdapter("myTopic", ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager);
     }
 
     @Test
     public void testPublish() throws ChannelException, IOException {
         String topicName = "myTopic";
         String message = "message";
-        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, mockAmqpBrokerConfig, mockAmqpConnectionManager);
+        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager);
 
         producerAdapter.publish(message);
 
@@ -70,7 +73,7 @@ public void testPublishWithRoutingKey() throws Exception{
         String topicName = "myTopic";
         String message = "message";
         String routingKey = "routingKey";
-        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, mockAmqpBrokerConfig, mockAmqpConnectionManager);
+        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, ExchangeType.TOPIC, mockAmqpBrokerConfig, mockAmqpConnectionManager);
 
         producerAdapter.publish(message, routingKey);
         verify(mockChannel).basicPublish(topicName, routingKey, MessageProperties.PERSISTENT_BASIC, message.getBytes());
@@ -82,7 +85,7 @@ public void testProperCharsetUsed() throws IOException {
 
         String message = "ö";
         byte[] expectedEncodedMessage = new byte[] { 0, 0, 0, -10 }; // In UTF-32 ö is mapped to 000000f6
-        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter("myTopic", mockAmqpBrokerConfig, mockAmqpConnectionManager);
+        AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter("myTopic", ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager);
 
         producerAdapter.publish(message);
 
diff --git a/amqp/src/test/java/io/github/tcdl/msb/api/AmqpResponderOptionsBuilderTest.java b/amqp/src/test/java/io/github/tcdl/msb/api/AmqpResponderOptionsBuilderTest.java
new file mode 100644
index 00000000..8f902d88
--- /dev/null
+++ b/amqp/src/test/java/io/github/tcdl/msb/api/AmqpResponderOptionsBuilderTest.java
@@ -0,0 +1,16 @@
+package io.github.tcdl.msb.api;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+public class AmqpResponderOptionsBuilderTest {
+
+    @Test
+    public void build_shouldSetHashBindingKeyByDefault() throws Exception {
+        ResponderOptions responderOptions = new AmqpResponderOptions.Builder().build();
+        assertEquals(Collections.singleton("#"), responderOptions.getBindingKeys());
+    }
+}
\ No newline at end of file
diff --git a/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java b/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java
index 7b60e634..a295ba9b 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/config/amqp/AmqpBrokerConfigTest.java
@@ -24,6 +24,7 @@ public class AmqpBrokerConfigTest {
     final boolean useSSL = false;
     final String groupId = "msb-java";
     final boolean durable = false;
+    final String exchangeType = "fanout";
     final int heartbeatIntervalSec = 1;
     final long networkRecoveryIntervalMs = 5000;
     final int prefetchCount = 1;
@@ -40,6 +41,7 @@ public void testBuildAmqpBrokerConfig() {
                 + " useSSL = \"" + useSSL + "\"\n"
                 + " groupId = \"" + groupId + "\"\n"
                 + " durable = " + durable + "\n"
+                + " defaultExchangeType = " + exchangeType + "\n"
                 + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n"
                 + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n"
                 + " prefetchCount = " + prefetchCount + "\n"
@@ -75,6 +77,7 @@ public void testOptionalConfigurationOptions() {
                 + " port = \"" + port + "\"\n"
                 + " useSSL = \"" + useSSL + "\"\n"
                 + " durable = " + durable + "\n"
+                + " defaultExchangeType = " + exchangeType + "\n"
                 + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n"
                 + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n"
                 + " prefetchCount = " + prefetchCount + "\n"
@@ -222,6 +225,7 @@ public void testHeartbeatIntervalOption() {
                 + " useSSL = \"" + useSSL + "\"\n"
                 + " groupId = \"" + groupId + "\"\n"
                 + " durable = " + durable + "\n"
+                + " defaultExchangeType = " + exchangeType + "\n"
                 + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n"
                 + " prefetchCount = " + prefetchCount + "\n"
                 + "}";
@@ -241,6 +245,7 @@ public void testNetworkRecoveryIntervalOption() {
                 + " useSSL = \"" + useSSL + "\"\n"
                 + " groupId = \"" + groupId + "\"\n"
                 + " durable = " + durable + "\n"
+                + " defaultExchangeType = " + exchangeType + "\n"
                 + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n"
                 + " prefetchCount = " + prefetchCount + "\n"
                 + "}";
@@ -260,6 +265,7 @@ public void testPrefetchCountOption() {
                 + " useSSL = \"" + useSSL + "\"\n"
                 + " groupId = \"" + groupId + "\"\n"
                 + " durable = " + durable + "\n"
+                + " defaultExchangeType = " + exchangeType + "\n"
                 + " heartbeatIntervalSec = " + heartbeatIntervalSec + "\n"
                 + " networkRecoveryIntervalMs = " + networkRecoveryIntervalMs + "\n"
                 + "}";
diff --git a/cli/pom.xml b/cli/pom.xml
index 92ff32fc..eaebce95 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2-SNAPSHOT
+        1.5.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/core/pom.xml b/core/pom.xml
index 5a83c822..e62043bf 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.2-SNAPSHOT
+        1.5.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
index 4c03b282..32b227c2 100644
--- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
@@ -2,15 +2,14 @@
 
 import java.time.Clock;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.github.tcdl.msb.adapters.*;
 import io.github.tcdl.msb.api.Callback;
-import io.github.tcdl.msb.api.MessageDestination;
+import io.github.tcdl.msb.api.RequestOptions;
+import io.github.tcdl.msb.api.ResponderOptions;
 import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException;
-import io.github.tcdl.msb.api.exception.MsbException;
 import io.github.tcdl.msb.collector.CollectorManager;
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.api.message.Message;
@@ -40,11 +39,8 @@ public class ChannelManager {
     private final MessageHandlerInvoker messageHandlerInvoker;
     private ChannelMonitorAgent channelMonitorAgent;
 
-    private final Map broadcastProducersByTopic;
-    private final Map multicastProducersByTopic;
-
-    private final Map broadcastConsumersByTopic;
-    private final Map multicastConsumersByTopic;
+    private final Map producersByTopic;
+    private final Map consumersByTopic;
 
     public ChannelManager(MsbConfig msbConfig, Clock clock, JsonValidator validator, ObjectMapper messageMapper, AdapterFactory adapterFactory, MessageHandlerInvoker messageHandlerInvoker) {
         this.msbConfig = msbConfig;
@@ -53,37 +49,20 @@ public ChannelManager(MsbConfig msbConfig, Clock clock, JsonValidator validator,
         this.messageMapper = messageMapper;
         this.adapterFactory = adapterFactory;
         this.messageHandlerInvoker = messageHandlerInvoker;
-        this.broadcastProducersByTopic = new ConcurrentHashMap<>();
-        this.multicastProducersByTopic = new ConcurrentHashMap<>();
-        this.broadcastConsumersByTopic = new ConcurrentHashMap<>();
-        this.multicastConsumersByTopic = new ConcurrentHashMap<>();
+
+        this.producersByTopic = new ConcurrentHashMap<>();
+        this.consumersByTopic = new ConcurrentHashMap<>();
 
         channelMonitorAgent = new NoopChannelMonitorAgent();
     }
 
-    public Producer findOrCreateProducer(final String topic) {
-        Validate.notNull(topic, "field 'topic' is null");
-        if(multicastProducersByTopic.containsKey(topic)){
-            throw new MsbException("Producer for " + topic + " already exists for multicast mode.");
-        }
-        Producer producer = broadcastProducersByTopic.computeIfAbsent(topic, key -> {
-            Producer newProducer = createProducer(key);
-            channelMonitorAgent.producerTopicCreated(key);
-            return newProducer;
-        });
+    public Producer findOrCreateProducer(String topic, RequestOptions requestOptions) {
+        Validate.notEmpty(topic, "Topic is mandatory");
+        Validate.notNull(requestOptions, "RequestOptions are mandatory");
 
-        return producer;
-    }
-
-    public Producer findOrCreateProducer(MessageDestination destination) {
-        Validate.notNull(destination, "destination is mandatory");
-        String topic = destination.getTopic();
-        if(broadcastProducersByTopic.containsKey(topic)){
-            throw new MsbException("Producer for " + topic + " already exists for broadcast mode.");
-        }
-        Producer producer = multicastProducersByTopic.computeIfAbsent(topic, namespace -> {
-            Producer newProducer = createProducer(destination);
-            channelMonitorAgent.producerTopicCreated(namespace);
+        Producer producer = producersByTopic.computeIfAbsent(topic, key -> {
+            Producer newProducer = createProducer(key, requestOptions);
+            channelMonitorAgent.producerTopicCreated(key);
             return newProducer;
         });
 
@@ -97,67 +76,33 @@ public Producer findOrCreateProducer(MessageDestination destination) {
      * @param messageHandler handler for processing messages
      * @throws ConsumerSubscriptionException if subscriber for topic already exist
      */
-    public synchronized void subscribe(String topic, Set routingKeys, MessageHandler messageHandler) {
-        Validate.notBlank(topic, "field 'topic' is empty");
-        Validate.notEmpty(routingKeys, "At least one routing key is required");
+    public synchronized void subscribe(String topic, ResponderOptions responderOptions, MessageHandler messageHandler) {
+        Validate.notBlank(topic, "Topic should not be empty");
+        Validate.notNull(responderOptions, "ResponderOptions are mandatory");
 
-        if(broadcastConsumersByTopic.get(topic) != null){
-            throw new ConsumerSubscriptionException("Consumer for " + topic + " already exists for broadcast mode.");
-        }
-
-        if (multicastConsumersByTopic.get(topic) != null) {
-            throw new ConsumerSubscriptionException("Subscriber for this topic " + topic + " already exist");
-        } else {
-            Consumer newConsumer = createConsumer(topic, routingKeys, new SimpleMessageHandlerResolverImpl(messageHandler, RESPONDER_LOGGING_NAME));
-            channelMonitorAgent.consumerTopicCreated(topic);
-            multicastConsumersByTopic.put(topic, newConsumer);
-        }
+        MessageHandlerResolver messageHandlerResolver = new SimpleMessageHandlerResolverImpl(messageHandler, RESPONDER_LOGGING_NAME);
+        subscribe(topic, false, responderOptions, messageHandlerResolver);
     }
 
     /**
-     * * Start consuming messages on specified topic with handler.
+     * {@link ChannelManager#subscribe(String, ResponderOptions, MessageHandler)} with default responderOptions
      */
     public void subscribe(String topic, MessageHandler messageHandler) {
-        Validate.notNull(topic, "field 'topic' is null");
-        Validate.notNull(messageHandler, "field 'messageHandler' is null");
-
-        if(multicastConsumersByTopic.get(topic) != null){
-            throw new ConsumerSubscriptionException("Consumer for " + topic + " already exists for multicast mode.");
-        }
-
-        if (broadcastConsumersByTopic.get(topic) != null) {
-            throw new ConsumerSubscriptionException("Subscriber for this topic " + topic + " already exist");
-        } else {
-            Consumer newConsumer = createConsumer(topic, false, new SimpleMessageHandlerResolverImpl(messageHandler, RESPONDER_LOGGING_NAME));
-            channelMonitorAgent.consumerTopicCreated(topic);
-            broadcastConsumersByTopic.put(topic, newConsumer);
-        }
+        subscribe(topic, ResponderOptions.DEFAULTS, messageHandler);
     }
 
     /**
-     * Start consuming response messages on specified topic and pass processing to CollectorManager.
-     * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages.
+     * Start consuming response messages.
      *
-     * @param topic
+     * @param topic response topic
      * @param collectorManager resolver of {@link MessageHandler}  for processing messages
      * @throws ConsumerSubscriptionException if subscriber for topic already exist
      */
-    public synchronized boolean subscribeForResponses(String topic, CollectorManager collectorManager) {
-        Validate.notNull(topic, "field 'topic' is null");
+    public synchronized void subscribeForResponses(String topic, CollectorManager collectorManager) {
+        Validate.notBlank(topic, "Topic should not be empty");
         Validate.notNull(collectorManager, "field 'collectorManager' is null");
 
-        if(multicastConsumersByTopic.get(topic) != null){
-            throw new ConsumerSubscriptionException("Consumer for " + topic + " already exists for multicast mode.");
-        }
-
-        if (broadcastConsumersByTopic.get(topic) != null) {
-            throw new ConsumerSubscriptionException("Subscriber for this topic: " + topic + " already exist");
-        } else {
-            Consumer newConsumer = createConsumer(topic, true, collectorManager);
-            broadcastConsumersByTopic.put(topic, newConsumer);
-            channelMonitorAgent.consumerTopicCreated(topic);
-            return false;
-        }
+        subscribe(topic, true, ResponderOptions.DEFAULTS, collectorManager);
     }
 
     /**
@@ -165,12 +110,19 @@ public synchronized boolean subscribeForResponses(String topic, CollectorManager
      * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages.
      */
     public synchronized void unsubscribe(String topic) {
-        if(broadcastConsumersByTopic.get(topic) != null){
-            stopConsumer(topic, broadcastConsumersByTopic.remove(topic));
+        if (consumersByTopic.get(topic) != null) {
+            stopConsumer(topic, consumersByTopic.remove(topic));
         }
+    }
 
-        if(multicastConsumersByTopic.get(topic) != null){
-            stopConsumer(topic, multicastConsumersByTopic.remove(topic));
+    private void subscribe(String topic, boolean isResponseTopic, ResponderOptions responderOptions, MessageHandlerResolver messageHandlerResolver) {
+        if (consumersByTopic.get(topic) != null) {
+            throw new ConsumerSubscriptionException("Subscriber for topic " + topic + " already exist");
+        } else {
+            Consumer newConsumer = createConsumer(topic, isResponseTopic, responderOptions, messageHandlerResolver);
+            newConsumer.subscribe();
+            channelMonitorAgent.consumerTopicCreated(topic);
+            consumersByTopic.put(topic, newConsumer);
         }
     }
 
@@ -181,35 +133,16 @@ private void stopConsumer(String topic, Consumer consumer) {
         }
     }
 
-    private Producer createProducer(String topic) {
+    private Producer createProducer(String topic, RequestOptions requestOptions) {
         Utils.validateTopic(topic);
-
-        ProducerAdapter adapter = getAdapterFactory().createProducerAdapter(topic);
-        Callback handler = message -> channelMonitorAgent.producerMessageSent(topic);
-        return new Producer(adapter, topic, handler, messageMapper);
+        ProducerAdapter adapter = this.adapterFactory.createProducerAdapter(topic, requestOptions);
+        Callback monitorAgentCallback = message -> channelMonitorAgent.producerMessageSent(topic);
+        return new Producer(adapter, topic, monitorAgentCallback, messageMapper);
     }
 
-    private Producer createProducer(MessageDestination destination) {
-        String topic = destination.getTopic();
-        Utils.validateTopic(topic);
-
-        ProducerAdapter adapter = getAdapterFactory().createProducerAdapter(destination);
-        Callback handler = message -> channelMonitorAgent.producerMessageSent(topic);
-        return new Producer(adapter, topic, handler, messageMapper);
-    }
-
-    private Consumer createConsumer(String topic, boolean isResponseTopic, MessageHandlerResolver messageHandlerResolver) {
-        ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic, isResponseTopic);
-        return createConsumer(topic, adapter, messageHandlerResolver);
-    }
-
-    private Consumer createConsumer(String topic, Set routingKeys, MessageHandlerResolver messageHandlerResolver) {
-        ConsumerAdapter adapter = getAdapterFactory().createConsumerAdapter(topic, routingKeys);
-        return createConsumer(topic, adapter, messageHandlerResolver);
-    }
-
-    private Consumer createConsumer(String topic, ConsumerAdapter adapter, MessageHandlerResolver messageHandlerResolver){
+    private Consumer createConsumer(String topic, boolean isResponseTopic, ResponderOptions responderOptions, MessageHandlerResolver messageHandlerResolver) {
         Utils.validateTopic(topic);
+        ConsumerAdapter adapter = this.adapterFactory.createConsumerAdapter(topic, responderOptions, isResponseTopic);
         return new Consumer(adapter, messageHandlerInvoker, topic, messageHandlerResolver, msbConfig, clock, channelMonitorAgent, validator, messageMapper);
     }
 
@@ -220,11 +153,6 @@ public void shutdown() {
         LOG.info("Shutdown complete");
     }
 
-    private AdapterFactory getAdapterFactory() {
-        return this.adapterFactory;
-    }
-
-
     public void setChannelMonitorAgent(ChannelMonitorAgent channelMonitorAgent) {
         this.channelMonitorAgent = channelMonitorAgent;
     }
diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java
index 92981535..ecd062c8 100644
--- a/core/src/main/java/io/github/tcdl/msb/Consumer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java
@@ -78,12 +78,17 @@ public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvoker messageHandler
         this.validator = validator;
         this.messageMapper = messageMapper;
 
-        this.rawAdapter.subscribe(this::handleRawMessage);
-
         this.loggingTag = String.format("[Consumer for: '%s' on topic: '%s']", messageHandlerResolver.getLoggingName(), topic);
         this.isSplitTagsForMdcLogging = !StringUtils.isEmpty(msbConfig.getMdcLoggingSplitTagsBy());
     }
 
+    /**
+     * Start consuming messages
+     */
+    public void subscribe() {
+        this.rawAdapter.subscribe(this::handleRawMessage);
+    }
+
     /**
      * Stop consuming messages for specified topic.
      */
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
index 8219ba7f..210ef477 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
@@ -1,11 +1,10 @@
 package io.github.tcdl.msb.adapters;
 
-import io.github.tcdl.msb.api.MessageDestination;
-import io.github.tcdl.msb.config.MsbConfig;
+import io.github.tcdl.msb.api.RequestOptions;
+import io.github.tcdl.msb.api.ResponderOptions;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
-
-import java.util.Set;
+import io.github.tcdl.msb.config.MsbConfig;
 
 /**
  * MSBAdapterFactory interface represents a common way for creation a particular AdapterFactory
@@ -25,15 +24,14 @@ public interface AdapterFactory {
      * @param topic topic name
      * @return Producer Adapter associated with a topic
      * @throws ChannelException if some problems during creation were occurred
+     * @deprecated use {@link AdapterFactory#createProducerAdapter(String, RequestOptions)}
      */
-    ProducerAdapter createProducerAdapter(String topic);
+    @Deprecated
+    default ProducerAdapter createProducerAdapter(String topic) {
+        return createProducerAdapter(topic, RequestOptions.DEFAULTS);
+    }
 
-    /**
-     * @param destination message destination
-     * @return Producer Adapter associated with a topic
-     * @throws ChannelException if some problems during creation were occurred
-     */
-    ProducerAdapter createProducerAdapter(MessageDestination destination);
+    ProducerAdapter createProducerAdapter(String topic, RequestOptions requestOptions);
 
     /**
      * @param topic topic name
@@ -44,14 +42,11 @@ public interface AdapterFactory {
     ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic);
 
     /**
-     * Creates ConsumerAdapter associated with a topic. Implementation must guarantee
-     * that bindings by provided routing keys are created. However, it does not guarantee
-     * that other bindings for the same queue do or do not exist.
+     * Creates ConsumerAdapter associated with a topic.
      * @param topic topic name
-     * @param routingKeys routing keys to be used for binding
      * @throws ChannelException if a problems has occurred during creation
      */
-    ConsumerAdapter createConsumerAdapter(String topic, Set routingKeys);
+    ConsumerAdapter createConsumerAdapter(String topic, ResponderOptions responderOptions, boolean isResponseTopic);
 
     /**
      * @return true if custom MSB threading model should be used.
diff --git a/core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java b/core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java
deleted file mode 100644
index 606c383e..00000000
--- a/core/src/main/java/io/github/tcdl/msb/api/MessageDestination.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package io.github.tcdl.msb.api;
-
-import org.apache.commons.lang3.Validate;
-
-public class MessageDestination {
-
-    private final String topic;
-    private final String routingKey;
-
-    public MessageDestination(String topic, String routingKey) {
-        Validate.notNull(topic, "topic is mandatory");
-        Validate.notNull(routingKey, "routingKey is mandatory");
-        this.topic = topic;
-        this.routingKey = routingKey;
-    }
-
-    public String getTopic() {
-        return topic;
-    }
-
-    public String getRoutingKey() {
-        return routingKey;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        MessageDestination that = (MessageDestination) o;
-
-        if (!topic.equals(that.topic)) return false;
-        return routingKey.equals(that.routingKey);
-
-    }
-
-    @Override
-    public int hashCode() {
-        int result = topic.hashCode();
-        result = 31 * result + routingKey.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "MessageDestination{" +
-                "topic='" + topic + '\'' +
-                ", routingKey='" + routingKey + '\'' +
-                '}';
-    }
-}
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
index d5a62dd9..0952c3b5 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
@@ -9,7 +9,7 @@
 import java.util.Set;
 
 /**
- * Provides methods for creation client-facing API classes.
+ * Provides methods for creation client-facing API objects.
  */
 public interface ObjectFactory {
 
@@ -37,49 +37,54 @@ public Type getType() {
     }
 
     /**
-     * @param namespace             topic name to send a request to
-     * @param requestOptions        options to configure a requester
-     * @param payloadTypeReference  expected payload type of response messages
-     * @return new instance of a {@link Requester} with original message
+     * @param namespace            topic name to send a request to
+     * @param requestOptions       options to configure a requester
+     * @param payloadTypeReference expected payload type of response messages
+     * @return new instance of a {@link Requester}
      */
      Requester createRequester(String namespace, RequestOptions requestOptions, TypeReference payloadTypeReference);
 
     /**
      * Same as
-     * {@link ObjectFactory#createRequesterForSingleResponse(java.lang.String, java.lang.Class, io.github.tcdl.msb.api.RequestOptions)}
+     * {@link #createRequesterForSingleResponse(String, Class, RequestOptions)}
      * with default request options
      */
-     Requester createRequesterForSingleResponse(String namespace, Class payloadClass);
+    default  Requester createRequesterForSingleResponse(String namespace, Class payloadClass) {
+        return createRequesterForSingleResponse(namespace, payloadClass, RequestOptions.DEFAULTS);
+    }
 
     /**
-     * Creates requester for single response with default response and acknowledgment timeouts
+     * Creates requester for single response
      *
      * @param namespace          topic name to send a request to
      * @param payloadClass       expected payload class of response messages
      * @param baseRequestOptions request options to be used as a source of response timeout and {@link MessageTemplate}.
      *                           Response time however will be 1 even if {@code baseRequestOptions} define other value.
-     * @return new instance of a {@link Requester} with original message
+     * @return new instance of a {@link Requester}
      */
      Requester createRequesterForSingleResponse(String namespace, Class payloadClass, RequestOptions baseRequestOptions);
 
     /**
      * Convenience method that specifies incoming payload type as {@link JsonNode}
      *
-     * See {@link #createRequester(String, RequestOptions, TypeReference)}
+     * @deprecated use {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)}
      */
+    @Deprecated
     default ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
-            ResponderServer.RequestHandler requestHandler) {
-        return createResponderServer(namespace, messageTemplate, requestHandler, JsonNode.class);
+                                                  ResponderServer.RequestHandler requestHandler) {
+        return createResponderServer(namespace, new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(), requestHandler, JsonNode.class);
     }
 
     /**
      * Convenience method that allows to specify incoming payload type via {@link Class}
      *
-     * See {@link #createRequester(String, RequestOptions, TypeReference)}
+     * @deprecated use {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)}
      */
+    @Deprecated
     default  ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate,
-            ResponderServer.RequestHandler requestHandler, Class payloadClass) {
-        return createResponderServer(namespace, messageTemplate, requestHandler, new TypeReference() {
+                                                      ResponderServer.RequestHandler requestHandler, Class payloadClass) {
+        ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build();
+        return createResponderServer(namespace, responderOptions, requestHandler, new TypeReference() {
             @Override
             public Type getType() {
                 return payloadClass;
@@ -88,130 +93,112 @@ public Type getType() {
     }
 
     /**
-     * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)}
-     */
-    default ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
-                                              ResponderServer.RequestHandler requestHandler){
-        return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, JsonNode.class);
-    }
-
-    /**
-     * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)}
+     * @param namespace            topic on a bus for listening on incoming requests
+     * @param responderOptions     {@link ResponderOptions} to be used
+     * @param requestHandler       handler for processing the request
+     * @param errorHandler         handler for errors to be called after default
+     * @param payloadTypeReference expected payload type of incoming messages
+     * @return new instance of a {@link ResponderServer} that unmarshals payload into specified payload type
      */
-    default  ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate,
-                                              ResponderServer.RequestHandler requestHandler, Class payloadClass) {
-        return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, null, payloadClass);
-    }
+     ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions,
+                                              ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference);
 
     /**
-     * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)}
+     * Convenience method that specifies incoming payload type as {@link JsonNode}
+     * 

+ * See {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)} */ - default ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) { - return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, errorHandler, new TypeReference() { + default ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, + ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler) { + return createResponderServer(namespace, responderOptions, requestHandler, errorHandler, new TypeReference() { @Override public Type getType() { - return payloadClass; + return JsonNode.class; } }); } /** - * See {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(java.lang.String, java.util.Set, io.github.tcdl.msb.api.MessageTemplate, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)} + * See {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)} */ - default ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate, + default ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) { - return createResponderServer(namespace, routingKeys, messageTemplate, requestHandler, null, payloadTypeReference); + return createResponderServer(namespace, responderOptions, requestHandler, null, payloadTypeReference); } /** - * @param namespace topic on a bus for listening on incoming requests - * @param routingKeys routing keys - * @param messageTemplate template of response message - * @param requestHandler handler for processing the request - * @param errorHandler handler for errors to be called after default - * @param payloadTypeReference expected payload type of incoming messages - * @return new instance of a {@link ResponderServer} that unmarshals payload into specified payload type + * See {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)} */ - ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference); + default ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, + ResponderServer.RequestHandler requestHandler, Class payloadClass) { + return createResponderServer(namespace, responderOptions, requestHandler, new TypeReference() { + @Override + public Type getType() { + return payloadClass; + } + }); + } - default ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) { - return createResponderServer(namespace, messageTemplate, requestHandler, errorHandler, new TypeReference() { + default ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, + ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) { + return createResponderServer(namespace, responderOptions, requestHandler, errorHandler, new TypeReference() { @Override public Type getType() { return payloadClass; + } }); } /** - * Same as - * {@link ObjectFactory#createRequesterForFireAndForget(java.lang.String, io.github.tcdl.msb.api.MessageTemplate)} - * with default messageTemplate + * @deprecated use {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)} */ - Requester createRequesterForFireAndForget(String namespace); + @Deprecated + default ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, + ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + return createResponderServer(namespace, responderOptions, requestHandler, errorHandler, payloadTypeReference); + } /** - * Creates requester that doesn't wait for any responses or acknowledgments - * - * @param namespace topic name to send a request to - * @param messageTemplate {@link MessageTemplate} to be used - * @return new instance of a {@link Requester} with original message + * @deprecated use {@link #createResponderServer(String, ResponderOptions, ResponderServer.RequestHandler, ResponderServer.ErrorHandler, TypeReference)} */ - Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate); + @Deprecated + default ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, + ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) { + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + return createResponderServer(namespace, responderOptions, requestHandler, errorHandler, new TypeReference() { + @Override + public Type getType() { + return payloadClass; + } + }); + } /** - * Creates requester that doesn't wait for any responses or acknowledgments - * - * @param namespace topic to send a request to - * @param forwardTo topic to be used for forwarding - * @param messageTemplate {@link MessageTemplate} to be used - * @return new instance of a {@link Requester} with original message + * See {@link #createRequesterForFireAndForget(java.lang.String, io.github.tcdl.msb.api.RequestOptions)} */ - Requester createRequesterForFireAndForget(String namespace, String forwardTo, MessageTemplate messageTemplate); + default Requester createRequesterForFireAndForget(String namespace){ + return createRequesterForFireAndForget(namespace, RequestOptions.DEFAULTS); + } /** * Creates requester that doesn't wait for any responses or acknowledgments * - * @param messageDestination {@link MessageDestination} to be used - * @param messageTemplate {@link MessageTemplate} to be used * @return new instance of a {@link Requester} with original message */ - Requester createRequesterForFireAndForget(MessageDestination messageDestination, MessageTemplate messageTemplate); + Requester createRequesterForFireAndForget(String namespace, RequestOptions requestOptions); /** - * Creates requester that doesn't wait for any responses or acknowledgments. - * - * @param namespace topic name to send a request to (where consumer with forwarding capabilities is expected) - * @param forwardTo {@link MessageDestination} to be used for forwarding - * @param messageTemplate {@link MessageTemplate} to be used - * @return new instance of a {@link Requester} with original message + * @deprecated use {@link ObjectFactory#createResponderServer(java.lang.String, io.github.tcdl.msb.api.ResponderOptions, io.github.tcdl.msb.api.ResponderServer.RequestHandler, io.github.tcdl.msb.api.ResponderServer.ErrorHandler, com.fasterxml.jackson.core.type.TypeReference)} */ - Requester createRequesterForFireAndForget(String namespace, MessageDestination forwardTo, MessageTemplate messageTemplate); - - /** - * @param namespace topic on a bus for listening on incoming requests - * @param messageTemplate template used for creating response messages - * @param requestHandler handler for processing the request - * @param payloadTypeReference expected payload type of incoming messages - * @return new instance of a {@link ResponderServer} that unmarshals payload into specified payload type - */ - ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference); - - /** - * @param namespace topic on a bus for listening on incoming requests - * @param messageTemplate template used for creating response messages - * @param requestHandler handler for processing the request - * @param errorHandler handler for errors to be called after default - * @param payloadTypeReference expected payload type of incoming messages - * @return new instance of a {@link ResponderServer} that unmarshals payload into specified payload type - */ - ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference); + @Deprecated + default ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, + ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) { + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + return createResponderServer(namespace, responderOptions, requestHandler, payloadTypeReference); + } /** * @return instance of converter to convert any objects diff --git a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java index 347e7b8a..57342746 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java +++ b/core/src/main/java/io/github/tcdl/msb/api/RequestOptions.java @@ -9,6 +9,7 @@ public class RequestOptions { public static final int WAIT_FOR_RESPONSES_UNTIL_TIMEOUT = -1; + public static final RequestOptions DEFAULTS = new Builder().build(); /** * Max time (in milliseconds) to wait for acknowledgements. */ @@ -38,7 +39,7 @@ public class RequestOptions { private final String routingKey; - private RequestOptions(Integer ackTimeout, Integer responseTimeout, Integer waitForResponses, MessageTemplate messageTemplate, String forwardNamespace, String routingKey) { + protected RequestOptions(Integer ackTimeout, Integer responseTimeout, Integer waitForResponses, MessageTemplate messageTemplate, String forwardNamespace, String routingKey) { this.ackTimeout = ackTimeout; this.responseTimeout = responseTimeout; this.waitForResponses = waitForResponses; @@ -57,7 +58,7 @@ public Integer getResponseTimeout() { public int getWaitForResponses() { if (waitForResponses == null || waitForResponses == -1) { - // use for Infinity number or expected responses + // use for infinite number or expected responses return WAIT_FOR_RESPONSES_UNTIL_TIMEOUT; } else { return waitForResponses; @@ -76,6 +77,9 @@ public String getRoutingKey() { return routingKey; } + public Builder asBuilder() { + return new RequestOptions.Builder().from(this); + } @Override public String toString() { @@ -89,12 +93,12 @@ public String toString() { public static class Builder { - private String routingKey; - private Integer ackTimeout; - private Integer responseTimeout; - private Integer waitForResponses; - private MessageTemplate messageTemplate; - private String forwardNamespace; + protected String routingKey; + protected Integer ackTimeout; + protected Integer responseTimeout; + protected Integer waitForResponses; + protected MessageTemplate messageTemplate; + protected String forwardNamespace; public Builder withRoutingKey(String routingKey) { this.routingKey = routingKey; @@ -130,7 +134,7 @@ public Builder withForwardNamespace(String forward) { * Convenience method to prepare Builder with properties equal to {@literal source} properties. * Is useful for cases when almost same RequestOptions except one or two properties are needed. */ - public Builder from(RequestOptions source) { + protected Builder from(RequestOptions source) { this.ackTimeout = source.ackTimeout; this.responseTimeout = source.responseTimeout; this.waitForResponses = source.waitForResponses; diff --git a/core/src/main/java/io/github/tcdl/msb/api/ResponderOptions.java b/core/src/main/java/io/github/tcdl/msb/api/ResponderOptions.java new file mode 100644 index 00000000..b56c31d2 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/api/ResponderOptions.java @@ -0,0 +1,60 @@ +package io.github.tcdl.msb.api; + +import joptsimple.internal.Strings; +import org.apache.commons.lang3.Validate; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.Set; + +/** + * Specifies options for {@link ResponderServer} + */ +public class ResponderOptions { + + private final Set bindingKeys; + private final MessageTemplate messageTemplate; + + public static final ResponderOptions DEFAULTS = new Builder().build(); + + protected ResponderOptions(Set bindingKeys, + MessageTemplate messageTemplate) { + + this.bindingKeys = Collections.unmodifiableSet(bindingKeys); + this.messageTemplate = messageTemplate; + } + + @Nonnull + public Set getBindingKeys() { + return bindingKeys; + } + + public MessageTemplate getMessageTemplate() { + return messageTemplate; + } + + public static class Builder { + + protected Set bindingKeys; + protected MessageTemplate messageTemplate; + + /** + * Each invocation REPLACES the old set of binding keys. Last one wins. + */ + public Builder withBindingKeys(Set bindingKeys) { + this.bindingKeys = bindingKeys; + return this; + } + + public Builder withMessageTemplate(MessageTemplate responseMessageTemplate) { + this.messageTemplate = responseMessageTemplate; + return this; + } + + public ResponderOptions build() { + return new ResponderOptions( + bindingKeys == null ? Collections.singleton(Strings.EMPTY) : bindingKeys, + messageTemplate == null ? new MessageTemplate() : messageTemplate); + } + } +} diff --git a/core/src/main/java/io/github/tcdl/msb/api/exception/AdapterCreationException.java b/core/src/main/java/io/github/tcdl/msb/api/exception/AdapterCreationException.java new file mode 100644 index 00000000..a8196da7 --- /dev/null +++ b/core/src/main/java/io/github/tcdl/msb/api/exception/AdapterCreationException.java @@ -0,0 +1,11 @@ +package io.github.tcdl.msb.api.exception; + +public class AdapterCreationException extends MsbException { + public AdapterCreationException(String message) { + super(message); + } + + public AdapterCreationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java index 22b7e3a6..1ee19e30 100644 --- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java +++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java @@ -9,6 +9,7 @@ import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.events.EventHandlers; import io.github.tcdl.msb.impl.MessageContextImpl; import io.github.tcdl.msb.impl.MsbContextImpl; @@ -16,6 +17,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.concurrent.NotThreadSafe; import java.time.Clock; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -36,6 +38,7 @@ /** * {@link Collector} is a component which collects responses and acknowledgements for sent requests. */ +@NotThreadSafe public class Collector implements ConsumedMessagesAwareMessageHandler, ExecutionOptionsAwareMessageHandler { private static final Logger LOG = LoggerFactory.getLogger(Collector.class); @@ -73,6 +76,7 @@ public class Collector implements ConsumedMessagesAwareMessageHandler, Execut private ScheduledFuture ackTimeoutFuture; private ScheduledFuture responseTimeoutFuture; private final CollectorManager collectorManager; + private final MsbConfig msbConfig; /** * Count of consumed incoming messages so {@link #handleMessage} invocation is expected in future. @@ -112,6 +116,7 @@ public Collector(String topic, Message requestMessage, RequestOptions requestOpt TypeReference payloadTypeReference, boolean directlyInvokableCallbacks) { this.requestMessage = requestMessage; + this.msbConfig = msbContext.getMsbConfig(); this.clock = msbContext.getClock(); this.collectorManager = msbContext.getCollectorManagerFactory().findOrCreateCollectorManager(topic); this.timeoutManager = msbContext.getTimeoutManager(); @@ -311,8 +316,9 @@ private int getMaxTimeoutMs() { for (String responderId : timeoutMsById.keySet()) { // Use only what we're waiting for if (!responsesRemainingById.isEmpty() && responsesRemainingById.containsKey(responderId) - && responsesRemainingById.get(responderId) == 0) + && responsesRemainingById.get(responderId) == 0) { continue; + } maxTimeoutMs = Math.max(timeoutMsById.get(responderId), maxTimeoutMs); } @@ -387,7 +393,7 @@ void waitForAcks() { private int getResponseTimeoutFromConfigs(RequestOptions requestOptions) { if (requestOptions.getResponseTimeout() == null) { - return 3000; + return msbConfig.getDefaultResponseTimeout(); } return requestOptions.getResponseTimeout(); } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java index 33e5850b..f5bf89c2 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java @@ -6,13 +6,13 @@ import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.monitor.aggregator.DefaultChannelMonitorAggregator; +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Type; import java.util.Collections; -import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; @@ -38,113 +38,43 @@ public Requester createRequester(String namespace, RequestOptions request return RequesterImpl.create(namespace, requestOptions, msbContext, payloadTypeReference); } - /** - * {@inheritDoc} - */ - @Override - public Requester createRequesterForSingleResponse(String namespace, Class payloadClass) { - MsbConfig msbConfig = msbContext.getMsbConfig(); - - RequestOptions requestOptions = new RequestOptions.Builder() - .withMessageTemplate(new MessageTemplate()) - .withResponseTimeout(msbConfig.getDefaultResponseTimeout()) - .build(); - - return createRequesterForSingleResponse(namespace, payloadClass, requestOptions); - } - /** * {@inheritDoc} */ @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, RequestOptions baseRequestOptions) { - - RequestOptions requestOptions = new RequestOptions.Builder().from(baseRequestOptions) + Validate.notNull(baseRequestOptions); + RequestOptions singleResponseRequestOptions = baseRequestOptions + .asBuilder() .withWaitForResponses(1) - .withAckTimeout(0) .build(); - return RequesterImpl.create(namespace, requestOptions, msbContext, toTypeReference(payloadClass)); - } - /** - * {@inheritDoc} - */ - @Override - public ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { - return ResponderServerImpl.create(namespace, routingKeys, messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference); + return RequesterImpl.create(namespace, singleResponseRequestOptions, msbContext, toTypeReference(payloadClass)); } /** * {@inheritDoc} */ @Override - public Requester createRequesterForFireAndForget(String namespace) { - return createRequesterForFireAndForget(namespace, null); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate){ - RequestOptions.Builder optionsBuilder = new RequestOptions.Builder() - .withMessageTemplate(messageTemplate) - .withWaitForResponses(0); - - return RequesterImpl.create(namespace, optionsBuilder.build(), msbContext, null); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace, String forwardTo, MessageTemplate messageTemplate) { - RequestOptions.Builder optionsBuilder = new RequestOptions.Builder() - .withForwardNamespace(forwardTo) - .withMessageTemplate(messageTemplate) - .withWaitForResponses(0); - - return RequesterImpl.create(namespace, optionsBuilder.build(), msbContext, null); - } - - /** - * {@inheritDoc} - */ - @Override - public Requester createRequesterForFireAndForget(MessageDestination destination, MessageTemplate messageTemplate) { - RequestOptions.Builder optionsBuilder = new RequestOptions.Builder() - .withMessageTemplate(messageTemplate) - .withRoutingKey(destination.getRoutingKey()) - .withWaitForResponses(0); - - return RequesterImpl.create(destination.getTopic(), optionsBuilder.build(), msbContext, null); + public ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, + ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { + return ResponderServerImpl.create(namespace, responderOptions, msbContext, requestHandler, errorHandler, payloadTypeReference); } /** * {@inheritDoc} */ @Override - public Requester createRequesterForFireAndForget(String namespace, MessageDestination forwardTo, MessageTemplate messageTemplate) { - RequestOptions.Builder optionsBuilder = new RequestOptions.Builder() - .withMessageTemplate(messageTemplate) - .withForwardNamespace(forwardTo.getTopic()) - .withRoutingKey(forwardTo.getRoutingKey()) - .withWaitForResponses(0); - - return RequesterImpl.create(namespace, optionsBuilder.build(), msbContext, null); - } + public Requester createRequesterForFireAndForget(String namespace, RequestOptions requestOptions) { + Validate.notNull(requestOptions, "RequestOptions are mandatory"); - /** - * {@inheritDoc} - */ - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) { - return createResponderServer(namespace, messageTemplate, requestHandler, null, payloadTypeReference); - } + RequestOptions fireAndForgetRequestOptions = requestOptions + .asBuilder() + .withAckTimeout(0) + .withWaitForResponses(0) + .build(); - /** - * {@inheritDoc} - */ - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { - return ResponderServerImpl.create(namespace, Collections.emptySet(), messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference); + return RequesterImpl.create(namespace, fireAndForgetRequestOptions, msbContext, null); } /** diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java index faa485fa..7416e463 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java @@ -177,13 +177,7 @@ private void publish(boolean invokeHandlersDirectly, Object requestPayload, Mess } private void publishMessage(Message message) { - Topics topics = message.getTopics(); - if (StringUtils.isBlank(topics.getForward()) && StringUtils.isNotBlank(topics.getRoutingKey())) { - MessageDestination destination = new MessageDestination(topics.getTo(), topics.getRoutingKey()); - getChannelManager().findOrCreateProducer(destination).publish(message); - } else { - getChannelManager().findOrCreateProducer(topics.getTo()).publish(message); - } + getChannelManager().findOrCreateProducer(message.getTopics().getTo(), requestOptions).publish(message); } private boolean isWaitForAckMs() { diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java index 42715a7a..d6fa9794 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java @@ -4,6 +4,7 @@ import io.github.tcdl.msb.Producer; import io.github.tcdl.msb.api.AcknowledgementHandler; import io.github.tcdl.msb.api.MessageTemplate; +import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Acknowledge.Builder; import io.github.tcdl.msb.api.message.Message; @@ -60,7 +61,7 @@ public void send(Object responsePayload) { } private void sendMessage(Message message) { - Producer producer = channelManager.findOrCreateProducer(message.getTopics().getTo()); + Producer producer = channelManager.findOrCreateProducer(message.getTopics().getTo(), RequestOptions.DEFAULTS); LOG.debug("Publishing message to topic : {}", message.getTopics().getTo()); producer.publish(message); } diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java index 95965838..c6627a38 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java @@ -18,24 +18,22 @@ public class ResponderServerImpl implements ResponderServer { private static final Logger LOG = LoggerFactory.getLogger(ResponderServerImpl.class); private String namespace; - private Set routingKeys; + private ResponderOptions responderOptions; private MsbContextImpl msbContext; - private MessageTemplate messageTemplate; private RequestHandler requestHandler; private Optional errorHandler; private ObjectMapper payloadMapper; private TypeReference payloadTypeReference; private ResponderServerImpl(String namespace, - Set routingKeys, - MessageTemplate messageTemplate, + ResponderOptions responderOptions, MsbContextImpl msbContext, RequestHandler requestHandler, ErrorHandler errorHandler, TypeReference payloadTypeReference) { + Validate.notNull(responderOptions); this.namespace = namespace; - this.routingKeys = routingKeys; - this.messageTemplate = messageTemplate; + this.responderOptions = responderOptions; this.msbContext = msbContext; this.requestHandler = requestHandler; this.errorHandler = Optional.ofNullable(errorHandler); @@ -47,9 +45,9 @@ private ResponderServerImpl(String namespace, /** * {@link io.github.tcdl.msb.api.ObjectFactory#createResponderServer(String, MessageTemplate, RequestHandler, ErrorHandler, Class)} */ - static ResponderServerImpl create(String namespace, Set routingKeys, MessageTemplate messageTemplate, MsbContextImpl msbContext, + static ResponderServerImpl create(String namespace, ResponderOptions responderOptions, MsbContextImpl msbContext, RequestHandler requestHandler, ErrorHandler errorHandler, TypeReference payloadTypeReference) { - return new ResponderServerImpl<>(namespace, routingKeys, messageTemplate, msbContext, requestHandler, errorHandler, payloadTypeReference); + return new ResponderServerImpl<>(namespace, responderOptions, msbContext, requestHandler, errorHandler, payloadTypeReference); } /** @@ -71,12 +69,7 @@ public ResponderServer listen() { onResponder(responderContext); }; - if (routingKeys == null || routingKeys.isEmpty()) { - channelManager.subscribe(namespace, messageHandler); - } else { - channelManager.subscribe(namespace, routingKeys, messageHandler); - } - + channelManager.subscribe(namespace, responderOptions, messageHandler); return this; } @@ -89,7 +82,7 @@ public ResponderServer stop(){ Responder createResponder(Message incomingMessage) { if (isResponseNeeded(incomingMessage)) { - return new ResponderImpl(messageTemplate, incomingMessage, msbContext); + return new ResponderImpl(responderOptions.getMessageTemplate(), incomingMessage, msbContext); } else { return new NoopResponderImpl(incomingMessage); } diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java b/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java index cec48ae5..45f3554f 100644 --- a/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java +++ b/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java @@ -3,6 +3,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.Producer; import io.github.tcdl.msb.api.MessageTemplate; +import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; @@ -143,7 +144,7 @@ private void doAnnounce() { .withBody(topicInfoMap) .build(); - Producer producer = channelManager.findOrCreateProducer(Utils.TOPIC_ANNOUNCE); + Producer producer = channelManager.findOrCreateProducer(Utils.TOPIC_ANNOUNCE, RequestOptions.DEFAULTS); Message.Builder messageBuilder = messageFactory.createBroadcastMessageBuilder(Utils.TOPIC_ANNOUNCE, new MessageTemplate()); Message announcementMessage = messageFactory.createBroadcastMessage(messageBuilder, payload); diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java index 495befd3..dfb4e066 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java @@ -9,6 +9,7 @@ import io.github.tcdl.msb.adapters.AdapterFactory; import io.github.tcdl.msb.adapters.AdapterFactoryLoader; +import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.RequesterResponderIT; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.collector.CollectorManager; @@ -56,7 +57,7 @@ public void testProducerCachedMultithreadInteraction() { String topic = "topic:test-producer-cached-multithreaded"; new MultithreadingTester().add(() -> { - channelManager.findOrCreateProducer(topic); + channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); verify(mockChannelMonitorAgent).producerTopicCreated(topic); }).run(); } @@ -80,7 +81,7 @@ public void testPublishMessageInvokesAgentMultithreadInteraction() throws Interr int numberOfThreads = 10; int numberOfInvocationsPerThread = 20; - Producer producer = channelManager.findOrCreateProducer(topic); + Producer producer = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); Message message = TestUtils.createSimpleRequestMessage(topic); CountDownLatch messagesSent = new CountDownLatch(numberOfThreads * numberOfInvocationsPerThread); @@ -100,12 +101,12 @@ public void testReceiveMessageInvokesAgentAndEmitsEventMultithreadInteraction() int numberOfThreads = 4; int numberOfInvocationsPerThread = 20; - Producer producer = channelManager.findOrCreateProducer(topic); + Producer producer = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); Message message = TestUtils.createSimpleRequestMessage(topic); CountDownLatch messagesReceived = new CountDownLatch(numberOfThreads * numberOfInvocationsPerThread); - channelManager.findOrCreateProducer(topic); + channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); channelManager.subscribe(topic, (msg, acknowledgeHandler) -> { messagesReceived.countDown(); diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java index a9e7189d..ed36e927 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java @@ -1,36 +1,33 @@ package io.github.tcdl.msb; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - +import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.adapters.AdapterFactory; import io.github.tcdl.msb.adapters.AdapterFactoryLoader; -import io.github.tcdl.msb.api.MessageDestination; +import io.github.tcdl.msb.api.RequestOptions; +import io.github.tcdl.msb.api.ResponderOptions; import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException; -import io.github.tcdl.msb.api.exception.MsbException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.TestUtils; - -import java.time.Clock; -import java.util.Collections; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import javax.xml.ws.Holder; - import io.github.tcdl.msb.threading.ConsumerExecutorFactoryImpl; import io.github.tcdl.msb.threading.MessageHandlerInvoker; import io.github.tcdl.msb.threading.ThreadPoolMessageHandlerInvoker; import org.junit.Before; import org.junit.Rule; import org.junit.Test; - -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.rules.ExpectedException; +import javax.xml.ws.Holder; +import java.time.Clock; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + public class ChannelManagerTest { private ChannelManager channelManager; @@ -60,81 +57,47 @@ public void testProducerCached() { String topic = "topic:test-producer-cached"; // Producer was created and monitor agent notified - Producer producer1 = channelManager.findOrCreateProducer(topic); + Producer producer1 = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); assertNotNull(producer1); verify(mockChannelMonitorAgent).producerTopicCreated(topic); // Cached producer was returned and monitor agent wasn't notified - Producer producer2 = channelManager.findOrCreateProducer(topic); + Producer producer2 = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); assertNotNull(producer2); assertSame(producer1, producer2); verifyNoMoreInteractions(mockChannelMonitorAgent); } - @Test(expected = ConsumerSubscriptionException.class) - public void testConsumerSubscribeMultipleSameTopic() { - String topic = "topic:test-consumer-cached"; + @Test + public void testMultipleConsumersCantSubscribeOnTheSameTopic() { + String topic = "topic:test-consumer"; // Consumer was created and monitor agent notified channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); + expectedException.expect(ConsumerSubscriptionException.class); channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); } @Test - public void testSubscribeSameTopicDifferentRoutingKeys() throws Exception { + public void testCantSubscribeOnTheSameTopicWithDifferentRoutingKeys() throws Exception { String topic = "interesting:topic"; - String routingKey1 = "routing.key.one"; - String routingKey2 = "routing.key.two"; + String bindingKey1 = "routing.key.one"; + String bindingKey2 = "routing.key.two"; + + ResponderOptions responderOptions1 = new ResponderOptions.Builder().withBindingKeys(Collections.singleton(bindingKey1)).build(); + ResponderOptions responderOptions2 = new ResponderOptions.Builder().withBindingKeys(Collections.singleton(bindingKey2)).build(); - channelManager.subscribe(topic, Collections.singleton(routingKey1), (message, acknowledgeHandler) -> {}); + channelManager.subscribe(topic, responderOptions1, (message, acknowledgeHandler) -> {}); verify(mockChannelMonitorAgent).consumerTopicCreated(topic); expectedException.expect(ConsumerSubscriptionException.class); - channelManager.subscribe(topic, Collections.singleton(routingKey2), (message, acknowledgeHandler) -> {}); - } - - @Test - public void testFindOrCreateProducerForDestination() throws Exception { - - String topic = "interesting:topic"; - String routingKey1 = "routing.key.one"; - String routingKey2 = "routing.key.two"; - - MessageDestination destination1 = new MessageDestination(topic, routingKey1); - MessageDestination destination2 = new MessageDestination(topic, routingKey2); - - Producer producer1 = channelManager.findOrCreateProducer(destination1); - Producer producer2 = channelManager.findOrCreateProducer(destination2); - - assertEquals("Multicast producer must be the same for same topic", producer1, producer2); - } - - @Test - public void testFindOrCreateProducer_broadcastProducerAlreadyExists() throws Exception { - String topic = "interesting:topic"; - String routingKey = "routing.key"; - - MessageDestination destination = new MessageDestination(topic, routingKey); - channelManager.findOrCreateProducer(topic); - expectedException.expect(MsbException.class); - channelManager.findOrCreateProducer(destination); - } - - @Test - public void testFindOrCreateProducer_multicastProducerAlreadyExists() throws Exception { - String topic = "interesting:topic"; - String routingKey = "routing.key"; - - MessageDestination destination = new MessageDestination(topic, routingKey); - channelManager.findOrCreateProducer(destination); - expectedException.expect(MsbException.class); - channelManager.findOrCreateProducer(topic); + channelManager.subscribe(topic, responderOptions2, (message, acknowledgeHandler) -> {}); } @Test public void testPublishMessageInvokesAgent() { String topic = "topic:test-agent-publish"; - Producer producer = channelManager.findOrCreateProducer(topic); + Producer producer = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); Message message = TestUtils.createSimpleRequestMessage(topic); producer.publish(message); @@ -151,11 +114,11 @@ public void testReceiveMessageInvokesAgentAndEmitsEvent() throws InterruptedExce Message message = TestUtils.createSimpleRequestMessage(topic); channelManager.subscribe(topic, - (msg, acknowledgeHandler) -> { + (msg, acknowledgeHandler) -> { messageEvent.value = msg; awaitReceiveEvents.countDown(); }); - channelManager.findOrCreateProducer(topic).publish(message); + channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS).publish(message); assertTrue(awaitReceiveEvents.await(4000, TimeUnit.MILLISECONDS)); verify(mockChannelMonitorAgent).consumerMessageReceived(topic); @@ -166,7 +129,8 @@ public void testReceiveMessageInvokesAgentAndEmitsEvent() throws InterruptedExce public void testSubscribeUnsubscribeFromBroadcast() { String topic = "topic:test-unsubscribe-once"; - channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); + channelManager.subscribe(topic, (message, acknowledgeHandler) -> { + }); channelManager.unsubscribe(topic); verify(mockChannelMonitorAgent).consumerTopicRemoved(topic); @@ -176,7 +140,10 @@ public void testSubscribeUnsubscribeFromBroadcast() { public void testSubscribeUnsubscribeFromMulticast() { String topic = "topic:test-unsubscribe-once"; - channelManager.subscribe(topic, Collections.singleton("routing.key"), (message, acknowledgeHandler) -> {}); + ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.singleton("routing.key")).build(); + + channelManager.subscribe(topic, responderOptions, (message, acknowledgeHandler) -> { + }); channelManager.unsubscribe(topic); verify(mockChannelMonitorAgent).consumerTopicRemoved(topic); @@ -187,8 +154,10 @@ public void testSubscribeUnsubscribeSeparateTopics() { String topic1 = "topic:test-unsubscribe-try-first"; String topic2 = "topic:test-unsubscribe-try-other"; - channelManager.subscribe(topic1, (message, acknowledgeHandler) -> {}); - channelManager.subscribe(topic2, (message, acknowledgeHandler) -> {}); + channelManager.subscribe(topic1, (message, acknowledgeHandler) -> { + }); + channelManager.subscribe(topic2, (message, acknowledgeHandler) -> { + }); channelManager.unsubscribe(topic1); verify(mockChannelMonitorAgent).consumerTopicRemoved(topic1); diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index d804a7f4..b0728877 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -138,13 +138,6 @@ public void testCreateConsumerNullMessageMapper() { new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, null); } - @Test - public void testSubscribeAdapterSubscribed() { - new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); - - verify(adapterMock).subscribe(any(ConsumerAdapter.RawMessageHandler.class)); - } - @Test public void testValidMessageProcessedBySubscriber() throws JsonConversionException { Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); diff --git a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java index dc8857a8..afc7aaa7 100644 --- a/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java +++ b/core/src/test/java/io/github/tcdl/msb/api/RequesterResponderIT.java @@ -3,17 +3,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; + +import com.fasterxml.jackson.core.type.TypeReference; import io.github.tcdl.msb.api.message.Acknowledge; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.impl.MsbContextImpl; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java index 5ee34d7e..0f1e1f9b 100644 --- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java +++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java @@ -1065,7 +1065,7 @@ public void testProcessAckPerResponder() { @Test public void testProcessAckWillTakeDefaultTimeoutIsMaxAndNotCallTimerAgain() { int timeoutMs = 1500; - //will set default 3000; + when(msbConfigurationsMock.getDefaultResponseTimeout()).thenReturn(3000); when(requestOptionsMock.getResponseTimeout()).thenReturn(null); Collector collector = createCollector(); @@ -1207,13 +1207,13 @@ MessageContext createMessageContext(AcknowledgementHandler acknowledgementHandle private void notifyMessagesConsumed(Collector collector, int messagesCount) { for(int i = 0; i < messagesCount; i++) { - ((ConsumedMessagesAwareMessageHandler)collector).notifyMessageConsumed(); + collector.notifyMessageConsumed(); } } private void notifyMessagesLost(Collector collector, int messagesCount) { for(int i = 0; i < messagesCount; i++) { - ((ConsumedMessagesAwareMessageHandler)collector).notifyConsumedMessageIsLost(); + collector.notifyConsumedMessageIsLost(); } } diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java index 28e9f63d..84ce7e63 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java @@ -24,14 +24,12 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.*; /** * Created by rdro on 4/27/2015. @@ -39,8 +37,7 @@ @RunWith(MockitoJUnitRunner.class) public class RequesterImplTest { - private static final String BROADCAST_NAMESPACE = "test:hello:all"; - private static final String MULTICAST_NAMESPACE = "test:hello:everyone"; + private static final String TOPIC = "test:hello:all"; @Mock private ChannelManager channelManagerMock; @@ -64,16 +61,6 @@ public void testPublishNoWaitForResponses() throws Exception { verify(collectorMock, never()).waitForResponses(); } - @Test - public void testPublishWithRoutingKeyNoWaitForResponses() throws Exception { - RequesterImpl requester = initRequesterForResponsesWith("UK", 0, 0, 0, null, null, null, null); - - publishByAllMethods(requester); - - verify(collectorMock, never()).listenForResponses(); - verify(collectorMock, never()).waitForResponses(); - } - @Test public void testPublishWaitForResponses() throws Exception { RequesterImpl requester = initRequesterForResponsesWith(1, 0, 0, null, null, null, null); @@ -84,16 +71,6 @@ public void testPublishWaitForResponses() throws Exception { verify(collectorMock, times(4)).waitForResponses(); } - @Test - public void testPublishWithRoutingKeyWaitForResponses() throws Exception { - RequesterImpl requester = initRequesterForResponsesWith("UK", 1, 0, 0, null, null, null, null); - - publishByAllMethods(requester); - - verify(collectorMock, times(4)).listenForResponses(); - verify(collectorMock, times(4)).waitForResponses(); - } - private void publishByAllMethods(RequesterImpl requester) { Message originalMessage = TestUtils.createMsbRequestMessage("some:topic", "body text"); requester.publish(TestUtils.createSimpleRequestPayload()); @@ -352,7 +329,7 @@ public void testRequest_acknowledgeHandlerCancelsFutureOnTooManyResponses() thro public void testRequestMessage() throws Exception { ChannelManager channelManagerMock = mock(ChannelManager.class); Producer producerMock = mock(Producer.class); - when(channelManagerMock.findOrCreateProducer(BROADCAST_NAMESPACE)).thenReturn(producerMock); + when(channelManagerMock.findOrCreateProducer(eq(TOPIC), eq(RequestOptions.DEFAULTS))).thenReturn(producerMock); ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); MsbContextImpl msbContext = TestUtils.createMsbContextBuilder() @@ -361,7 +338,7 @@ public void testRequestMessage() throws Exception { .build(); RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); - Requester requester = RequesterImpl.create(BROADCAST_NAMESPACE, TestUtils.createSimpleRequestOptions(), msbContext, new TypeReference(){}); + Requester requester = RequesterImpl.create(TOPIC, RequestOptions.DEFAULTS, msbContext, new TypeReference(){}); requester.publish(requestPayload); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -375,7 +352,6 @@ public void testRequestMessage() throws Exception { public void testRequestMessageWithTags() throws Exception { ChannelManager channelManagerMock = mock(ChannelManager.class); Producer producerMock = mock(Producer.class); - when(channelManagerMock.findOrCreateProducer(BROADCAST_NAMESPACE)).thenReturn(producerMock); ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); MsbContextImpl msbContext = TestUtils.createMsbContextBuilder() @@ -390,7 +366,9 @@ public void testRequestMessageWithTags() throws Exception { RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); RequestOptions requestOptions = TestUtils.createSimpleRequestOptionsWithTags(tag); - Requester requester = RequesterImpl.create(BROADCAST_NAMESPACE, requestOptions, msbContext, new TypeReference(){}); + when(channelManagerMock.findOrCreateProducer(eq(TOPIC), eq(requestOptions))).thenReturn(producerMock); + + Requester requester = RequesterImpl.create(TOPIC, requestOptions, msbContext, new TypeReference(){}); requester.publish(requestPayload, dynamicTag1, dynamicTag2, nullTag); verify(producerMock).publish(messageArgumentCaptor.capture()); @@ -398,42 +376,6 @@ public void testRequestMessageWithTags() throws Exception { assertArrayEquals(new String[]{tag, dynamicTag1, dynamicTag2}, requestMessage.getTags().toArray()); } - @Test - public void testRequestMessageWithForward_shouldNotWaitForResponsesOrAcks() throws Exception { - String routingKey = "to.santa"; - - ChannelManager channelManagerMock = mock(ChannelManager.class); - Producer producerMock = mock(Producer.class); - when(channelManagerMock.findOrCreateProducer(BROADCAST_NAMESPACE)).thenReturn(producerMock); - ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); - - MsbContextImpl msbContext = TestUtils.createMsbContextBuilder() - .withChannelManager(channelManagerMock) - .withClock(Clock.systemDefaultZone()) - .build(); - - RestPayload requestPayload = TestUtils.createSimpleRequestPayload(); - RequestOptions requestOptions = new RequestOptions - .Builder() - .withRoutingKey(routingKey) - .withWaitForResponses(3) - .withAckTimeout(1) - .withForwardNamespace(MULTICAST_NAMESPACE).build(); - - RequesterImpl requesterSpy = spy(RequesterImpl.create(BROADCAST_NAMESPACE, requestOptions, msbContext, new TypeReference() {})); - requesterSpy.publish(requestPayload); - - verify(producerMock).publish(messageArgumentCaptor.capture()); - - //check collector wasn't set up - verify(requesterSpy, never()).createCollector(any(), any(), any(), any(), anyBoolean()); - verify(channelManagerMock, never()).findOrCreateProducer(any(MessageDestination.class)); - - //check message fields - Message requestMessage = messageArgumentCaptor.getValue(); - assertEquals(MULTICAST_NAMESPACE, requestMessage.getTopics().getForward()); - assertEquals(routingKey, requestMessage.getTopics().getRoutingKey()); - } private RequesterImpl initRequesterForResponsesWith(Integer numberOfResponses, Integer respTimeout, Integer ackTimeout, BiConsumer onResponse, BiConsumer onAcknowledge, @@ -447,27 +389,9 @@ private RequesterImpl initRequesterForResponsesWith(Integer numberO .withAckTimeout(ackTimeout) .build(); - when(channelManagerMock.findOrCreateProducer(anyString())).thenReturn(producerMock); - - return setUpRequester(BROADCAST_NAMESPACE, onResponse, onAcknowledge, onError, endHandler, requestOptions); - } - - private RequesterImpl initRequesterForResponsesWith(String routingKey, Integer numberOfResponses, Integer respTimeout, Integer ackTimeout, - BiConsumer onResponse, BiConsumer onAcknowledge, - BiConsumer onError, - Callback endHandler) throws Exception { - - RequestOptions requestOptions = new RequestOptions.Builder() - .withMessageTemplate(mock(MessageTemplate.class)) - .withWaitForResponses(numberOfResponses) - .withResponseTimeout(respTimeout) - .withRoutingKey(routingKey) - .withAckTimeout(ackTimeout) - .build(); - - when(channelManagerMock.findOrCreateProducer(any(MessageDestination.class))).thenReturn(producerMock); + when(channelManagerMock.findOrCreateProducer(anyString(), any(RequestOptions.class))).thenReturn(producerMock); - return setUpRequester(MULTICAST_NAMESPACE, onResponse, onAcknowledge, onError, endHandler, requestOptions); + return setUpRequester(TOPIC, onResponse, onAcknowledge, onError, endHandler, requestOptions); } private RequesterImpl setUpRequester(String namespace, BiConsumer onResponse, BiConsumer onAcknowledge, BiConsumer onError, Callback endHandler, RequestOptions requestOptions) { diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java index 65ccb2b4..a5167c6e 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; @@ -14,6 +15,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.Producer; import io.github.tcdl.msb.api.MessageTemplate; +import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.Responder; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.config.MsbConfig; @@ -56,7 +58,7 @@ public void setUp() { when(msbContextSpy.getChannelManager()).thenReturn(mockChannelManager); when(msbContextSpy.getMessageFactory()).thenReturn(spyMessageFactory); - when(mockChannelManager.findOrCreateProducer(anyString())).thenReturn(mockProducer); + when(mockChannelManager.findOrCreateProducer(anyString(), any(RequestOptions.class))).thenReturn(mockProducer); responder = new ResponderImpl(messageTemplate, originalMessage, msbContextSpy); } @@ -73,7 +75,7 @@ public void testProducerWasCreatedForProperTopic() { ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); responder.send(""); - verify(mockChannelManager).findOrCreateProducer(argument.capture()); + verify(mockChannelManager).findOrCreateProducer(argument.capture(), any(RequestOptions.class)); assertEquals(originalMessage.getTopics().getResponse(), argument.getValue()); } diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java index 78f7d45d..ffbf4494 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java @@ -5,12 +5,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.Producer; -import io.github.tcdl.msb.api.AcknowledgementHandler; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.Responder; -import io.github.tcdl.msb.api.ResponderContext; -import io.github.tcdl.msb.api.ResponderServer; +import io.github.tcdl.msb.api.*; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.support.TestUtils; @@ -28,12 +23,7 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class ResponderServerImplTest { @@ -69,13 +59,15 @@ public void testResponderServerProcessPayloadSuccess() throws Exception { when(spyMsbContext.getChannelManager()).thenReturn(spyChannelManager); - ResponderServerImpl, Object, Map>> responderServer = ResponderServerImpl - .create(TOPIC, Collections.emptySet(), requestOptions.getMessageTemplate(), spyMsbContext, handler, null, + ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.emptySet()).withMessageTemplate(messageTemplate).build(); + + ResponderServerImpl, Object, Map>> responderServer = + ResponderServerImpl.create(TOPIC,responderOptions, spyMsbContext, handler, null, new TypeReference, Object, Map>>() {}); ResponderServerImpl spyResponderServer = (ResponderServerImpl) spy(responderServer).listen(); - verify(spyChannelManager).subscribe(anyString(), subscriberCaptor.capture()); + verify(spyChannelManager).subscribe(anyString(), any(ResponderOptions.class), subscriberCaptor.capture()); assertNull("MessageContext must be absent outside message handler execution", MsbThreadContext.getMessageContext()); assertNull("Request must be absent outside message handler execution", MsbThreadContext.getRequest()); @@ -98,8 +90,10 @@ public void testResponderServerProcessUnexpectedPayload() throws Exception { String bodyText = "some body"; Message incomingMessage = TestUtils.createMsbRequestMessage(TOPIC, bodyText); + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, null, messageTemplate, msbContext, handler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, handler, null, new TypeReference() {}); responderServer.listen(); // simulate incoming request @@ -119,8 +113,10 @@ public void testResponderServerProcessHandlerThrowException() throws Exception { Exception error = new Exception(exceptionMessage); ResponderServer.RequestHandler handler = (request, responderContext) -> { throw error; }; + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, null, messageTemplate, msbContext, handler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, handler, null, new TypeReference() {}); responderServer.listen(); // simulate incoming request @@ -143,9 +139,10 @@ public void testResponderServerProcessCustomHandlerThrowException() throws Excep throw error; }; + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); ResponderServer.ErrorHandler errorHandlerMock = mock(ResponderServer.ErrorHandler.class); ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, null, messageTemplate, msbContext, handler, errorHandlerMock, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, handler, errorHandlerMock, new TypeReference() {}); responderServer.listen(); // simulate incoming request @@ -166,13 +163,14 @@ public void testCreateResponderWithResponseTopic() { ChannelManager mockChannelManager = mock(ChannelManager.class); Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(anyString())).thenReturn(mockProducer); + when(mockChannelManager.findOrCreateProducer(anyString(), any(RequestOptions.class))).thenReturn(mockProducer); MsbContextImpl msbContext1 = new TestUtils.TestMsbContextBuilder() .withChannelManager(mockChannelManager) .build(); + ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.emptySet()).withMessageTemplate(messageTemplate).build(); ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, null, messageTemplate, msbContext1, handler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext1, handler, null, new TypeReference() {}); Message incomingMessage = TestUtils.createMsbRequestMessageNoPayload(TOPIC); Responder responder = responderServer.createResponder(incomingMessage); @@ -194,14 +192,16 @@ public void testCreateResponderWithRoutingKeys() throws Exception { .withChannelManager(mockChannelManager) .build(); - Set routingKeys = Sets.newHashSet("routing.key.one", "routing.key.two"); + Set bindingKeys = Sets.newHashSet("routing.key.one", "routing.key.two"); ResponderServer.RequestHandler requestHandler = (request, responderContext) -> {}; + + ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(bindingKeys).withMessageTemplate(messageTemplate).build(); ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, routingKeys, messageTemplate, msbContext, requestHandler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, requestHandler, null, new TypeReference() {}); responderServer.listen(); - verify(mockChannelManager).subscribe(eq(TOPIC), eq(routingKeys), any(MessageHandler.class)); + verify(mockChannelManager).subscribe(eq(TOPIC), eq(responderOptions), any(MessageHandler.class)); } @Test @@ -213,11 +213,14 @@ public void testCreateResponderWithoutRoutingKeys() throws Exception { .build(); ResponderServer.RequestHandler requestHandler = (request, responderContext) -> {}; + + ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.emptySet()).withMessageTemplate(messageTemplate).build(); + ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, Collections.emptySet(), messageTemplate, msbContext, requestHandler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, requestHandler, null, new TypeReference() {}); responderServer.listen(); - verify(mockChannelManager).subscribe(eq(TOPIC), any(MessageHandler.class)); + verify(mockChannelManager).subscribe(eq(TOPIC), same(responderOptions), any(MessageHandler.class)); } @Test @@ -230,8 +233,10 @@ public void testCreateResponderNoResponseTopic() { .withChannelManager(mockChannelManager) .build(); + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + ResponderServerImpl responderServer = ResponderServerImpl - .create(TOPIC, null, messageTemplate, msbContext, handler, null, new TypeReference() {}); + .create(TOPIC, responderOptions, msbContext, handler, null, new TypeReference() {}); Message incomingMessage = TestUtils.createMsbBroadcastMessageNoPayload(TOPIC); Responder responder = responderServer.createResponder(incomingMessage); @@ -252,8 +257,10 @@ public void testStop() throws Exception { .withChannelManager(mockChannelManager) .build(); + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + ResponderServerImpl responderServer = ResponderServerImpl.create( - TOPIC, null, messageTemplate, msbContext, doNothingHandler, null, new TypeReference() {} + TOPIC, responderOptions, msbContext, doNothingHandler, null, new TypeReference() {} ); responderServer.stop(); diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java index 30b92e12..ccfcf3f8 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java @@ -3,11 +3,9 @@ import io.github.tcdl.msb.adapters.AdapterFactory; import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.adapters.ProducerAdapter; -import io.github.tcdl.msb.api.MessageDestination; +import io.github.tcdl.msb.api.RequestOptions; +import io.github.tcdl.msb.api.ResponderOptions; import io.github.tcdl.msb.config.MsbConfig; -import org.apache.commons.lang3.StringUtils; - -import java.util.Set; /** * This AdapterFactory implementation is used to capture/submit raw messages as JSON and could be used during testing. @@ -30,17 +28,9 @@ public void init(MsbConfig msbConfig) { } @Override - public ProducerAdapter createProducerAdapter(String namespace) { - TestMsbProducerAdapter producerAdapter = new TestMsbProducerAdapter(namespace, storage); - storage.addProducerAdapter(namespace, producerAdapter); - return producerAdapter; - } - - @Override - public ProducerAdapter createProducerAdapter(MessageDestination destination) { - String namespace = destination.getTopic(); - TestMsbProducerAdapter producerAdapter = new TestMsbProducerAdapter(namespace, storage); - storage.addProducerAdapter(namespace, producerAdapter); + public ProducerAdapter createProducerAdapter(String topic, RequestOptions requestOptions) { + TestMsbProducerAdapter producerAdapter = new TestMsbProducerAdapter(topic, storage); + storage.addProducerAdapter(topic, producerAdapter); return producerAdapter; } @@ -52,9 +42,9 @@ public ConsumerAdapter createConsumerAdapter(String namespace, boolean isRespons } @Override - public ConsumerAdapter createConsumerAdapter(String namespace, Set routingKeys) { - TestMsbConsumerAdapter consumerAdapter = new TestMsbConsumerAdapter(namespace, storage); - storage.addConsumerAdapter(namespace, routingKeys, consumerAdapter); + public ConsumerAdapter createConsumerAdapter(String topic, ResponderOptions responderOptions, boolean isResponseTopic) { + TestMsbConsumerAdapter consumerAdapter = new TestMsbConsumerAdapter(topic, storage); + storage.addConsumerAdapter(topic, responderOptions.getBindingKeys(), consumerAdapter); return consumerAdapter; } diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java index 5de65bb8..65066222 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java @@ -6,6 +6,7 @@ import io.github.tcdl.msb.api.monitor.AggregatorStats; import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; import io.github.tcdl.msb.impl.RequesterImpl; +import org.apache.commons.lang3.Validate; import java.lang.reflect.Type; import java.util.Set; @@ -29,20 +30,11 @@ public Requester createRequester(String namespace, RequestOptions request return capture.getRequesterMock(); } - @Override - public Requester createRequesterForSingleResponse(String namespace, Class payloadClass) { - RequestOptions requestOptions = new RequestOptions.Builder() - .withMessageTemplate(new MessageTemplate()) - .withResponseTimeout(100) - .build(); - - return createRequesterForSingleResponse(namespace, payloadClass, requestOptions); - } - @Override public Requester createRequesterForSingleResponse(String namespace, Class payloadClass, RequestOptions baseRequestOptions) { - - RequestOptions requestOptions = new RequestOptions.Builder().from(baseRequestOptions) + Validate.notNull(baseRequestOptions); + RequestOptions requestOptions = baseRequestOptions + .asBuilder() .withWaitForResponses(1) .withAckTimeout(0) .build(); @@ -65,95 +57,18 @@ public Requester createRequester(String namespace, RequestOptions request } @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, TypeReference payloadTypeReference) { - ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, payloadTypeReference, null); - storage.addCapture(capture); - return capture.getResponderServerMock(); - } - - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { - ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, errorHandler, payloadTypeReference, null); - storage.addCapture(capture); - return capture.getResponderServerMock(); - } - - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler) { - ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, null, null); - storage.addCapture(capture); - return capture.getResponderServerMock(); - } - - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, Class payloadClass) { - ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, null, null, payloadClass); + public ResponderServer createResponderServer(String namespace, ResponderOptions responderOptions, ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, TypeReference payloadTypeReference) { + ResponderCapture capture = new ResponderCapture<>(namespace, responderOptions.getBindingKeys(), responderOptions.getMessageTemplate(), requestHandler, null, payloadTypeReference, null); storage.addCapture(capture); return capture.getResponderServerMock(); } @Override - public ResponderServer createResponderServer(String namespace, Set routingKeys, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, - TypeReference payloadTypeReference) { + public Requester createRequesterForFireAndForget(String namespace, RequestOptions requestOptions) { + Validate.notNull(requestOptions); + RequestOptions fireAndForgetRequestOptions = requestOptions.asBuilder().withWaitForResponses(0).build(); - ResponderCapture capture = new ResponderCapture<>(namespace, routingKeys, messageTemplate, requestHandler, null, payloadTypeReference, null); - storage.addCapture(capture); - return capture.getResponderServerMock(); - } - - @Override - public ResponderServer createResponderServer(String namespace, MessageTemplate messageTemplate, - ResponderServer.RequestHandler requestHandler, ResponderServer.ErrorHandler errorHandler, Class payloadClass) { - ResponderCapture capture = new ResponderCapture<>(namespace, messageTemplate, requestHandler, errorHandler, null, payloadClass); - storage.addCapture(capture); - return capture.getResponderServerMock(); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace) { - return createRequesterForFireAndForget(namespace, null); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace, MessageTemplate messageTemplate) { - RequestOptions requestOptions = new RequestOptions.Builder() - .withWaitForResponses(0) - .build(); - return createRequester(namespace, requestOptions, (Class)null); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace, String forwardTo, MessageTemplate messageTemplate) { - RequestOptions.Builder optionsBuilder = new RequestOptions.Builder() - .withForwardNamespace(forwardTo) - .withMessageTemplate(messageTemplate) - .withWaitForResponses(0); - - return createRequester(namespace, optionsBuilder.build(), (Class)null); - } - - @Override - public Requester createRequesterForFireAndForget(MessageDestination destination, MessageTemplate messageTemplate) { - RequestOptions requestOptions = new RequestOptions.Builder() - .withWaitForResponses(0) - .withRoutingKey(destination.getRoutingKey()) - .build(); - return createRequester(destination.getTopic(), requestOptions, (Class)null); - } - - @Override - public Requester createRequesterForFireAndForget(String namespace, MessageDestination forwardTo, MessageTemplate messageTemplate) { - RequestOptions requestOptions = new RequestOptions.Builder() - .withWaitForResponses(0) - .withForwardNamespace(forwardTo.getTopic()) - .withRoutingKey(forwardTo.getRoutingKey()) - .build(); - return createRequester(namespace, requestOptions, (Class)null); + return createRequester(namespace, fireAndForgetRequestOptions, (Class) null); } @Override diff --git a/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java b/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java index 984be173..ffde68e4 100644 --- a/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java +++ b/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java @@ -3,6 +3,7 @@ import io.github.tcdl.msb.ChannelManager; import io.github.tcdl.msb.MessageHandler; import io.github.tcdl.msb.Producer; +import io.github.tcdl.msb.api.RequestOptions; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.collector.TimeoutManager; import io.github.tcdl.msb.config.ServiceDetails; @@ -25,7 +26,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -57,14 +60,14 @@ public void setUp() { public void testAnnounceProducerForServiceTopic() { channelMonitorAgent.producerTopicCreated(TOPIC_ANNOUNCE); - verify(mockChannelManager, never()).findOrCreateProducer(anyString()); + verify(mockChannelManager, never()).findOrCreateProducer(anyString(), any(RequestOptions.class)); } @Test public void testAnnounceProducerForNormalTopic() { String topicName = "search:parsers:facets:v1"; Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(TOPIC_ANNOUNCE)).thenReturn(mockProducer); + when(mockChannelManager.findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class))).thenReturn(mockProducer); // method under test channelMonitorAgent.producerTopicCreated(topicName); @@ -80,14 +83,14 @@ public void testAnnounceProducerForNormalTopic() { @Test public void testAnnounceConsumerForServiceTopic() { channelMonitorAgent.consumerTopicCreated(TOPIC_ANNOUNCE); - verify(mockChannelManager, never()).subscribe(anyString(), Mockito.any(MessageHandler.class)); + verify(mockChannelManager, never()).subscribe(anyString(), any(MessageHandler.class)); } @Test public void testAnnounceConsumerForNormalTopic() { String topicName = "search:parsers:facets:v1"; Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(TOPIC_ANNOUNCE)).thenReturn(mockProducer); + when(mockChannelManager.findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class))).thenReturn(mockProducer); // method under test channelMonitorAgent.consumerTopicCreated(topicName); @@ -104,7 +107,7 @@ public void testAnnounceConsumerForNormalTopic() { public void testRemoveConsumerForNormalTopic() { String topicName = "search:parsers:facets:v1"; Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(TOPIC_ANNOUNCE)).thenReturn(mockProducer); + when(mockChannelManager.findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class))).thenReturn(mockProducer); channelMonitorAgent.consumerTopicCreated(topicName); // subscribe to the topic as a preparation step // method under test @@ -139,11 +142,11 @@ public void testStart() { ChannelMonitorAgent startedAgent = channelMonitorAgent.start(); assertSame(channelMonitorAgent, startedAgent); - verify(mockChannelManager).subscribe(Mockito.eq(TOPIC_HEARTBEAT), Mockito.any(MessageHandler.class)); + verify(mockChannelManager).subscribe(eq(TOPIC_HEARTBEAT), any(MessageHandler.class)); } private Message verifyProducerInvokedAndReturnMessage(Producer mockProducer) { - verify(mockChannelManager).findOrCreateProducer(TOPIC_ANNOUNCE); + verify(mockChannelManager).findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class)); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); verify(mockProducer).publish(messageCaptor.capture()); return messageCaptor.getValue(); diff --git a/examples/pom.xml b/examples/pom.xml index 695cbb09..8c56bcb5 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.5.2-SNAPSHOT + 1.5.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java b/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java index a41acf66..64025572 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/ConsumerWithRoutingKeys.java @@ -18,15 +18,19 @@ public static void main(String[] args) { enableShutdownHook(true). build(); - MessageTemplate messageTemplate = new MessageTemplate(); ObjectFactory objectFactory = msbContext.getObjectFactory(); + + ResponderOptions responderOptions = new AmqpResponderOptions.Builder() + .withBindingKeys(Sets.newHashSet("zero", "two")) + .withExchangeType(ExchangeType.TOPIC) + .build(); + ResponderServer responderServer = objectFactory.createResponderServer("routing:namespace", - Sets.newHashSet("zero", "two"), - messageTemplate, + responderOptions, (request, responderContext) -> { LOG.info("Received message: {}", request); - }); + }, String.class); responderServer.listen(); } } diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java b/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java index 48f5940b..2b35d82f 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java @@ -1,9 +1,6 @@ package io.github.tcdl.msb.examples; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.MsbContext; -import io.github.tcdl.msb.api.MsbContextBuilder; -import io.github.tcdl.msb.api.Responder; +import io.github.tcdl.msb.api.*; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.examples.payload.Query; import io.github.tcdl.msb.examples.payload.Request; @@ -31,7 +28,9 @@ public void start(MsbContext msbContext) { MessageTemplate messageTemplate = new MessageTemplate().withTags("date-extractor"); final String namespace = "search:parsers:facets:v1"; - msbContext.getObjectFactory().createResponderServer(namespace, messageTemplate, (request, responderContext) -> { + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + + msbContext.getObjectFactory().createResponderServer(namespace, responderOptions, (request, responderContext) -> { Query query = request.getQuery(); String queryString = query.getQ(); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java b/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java index 6ff5dcb4..a6e663b1 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java @@ -1,12 +1,6 @@ package io.github.tcdl.msb.examples; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.MsbContext; -import io.github.tcdl.msb.api.MsbContextBuilder; -import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.Requester; -import io.github.tcdl.msb.api.Responder; -import io.github.tcdl.msb.api.ResponderContext; +import io.github.tcdl.msb.api.*; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.examples.payload.Request; @@ -36,8 +30,10 @@ public static void main(String[] args) throws ScriptException, FileNotFoundExcep MessageTemplate messageTemplate = new MessageTemplate().withTags("facets-aggregator"); final String namespace = "search:aggregator:facets:v1"; + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + msbContext.getObjectFactory().createResponderServer( - namespace, messageTemplate, (Request facetsRequest, ResponderContext responderContext) -> { + namespace, responderOptions, (Request facetsRequest, ResponderContext responderContext) -> { String q = facetsRequest.getQuery().getQ(); Responder responder = responderContext.getResponder(); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java b/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java index 338fbfb5..c1e4b65c 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/PongService.java @@ -14,7 +14,8 @@ public static void main(String[] args) { ObjectFactory objectFactory = msbContext.getObjectFactory(); MessageTemplate messageTemplate = new MessageTemplate().withTags("pong-static-tag"); - ResponderServer responderServer = objectFactory.createResponderServer("pingpong:namespace", messageTemplate, + ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build(); + ResponderServer responderServer = objectFactory.createResponderServer("pingpong:namespace", responderOptions, (request, responderContext) -> { // Response handling logic LOG.info(String.format("Handling %s...", request)); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java b/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java index 172ccf50..8efa4fd3 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/ProducerWithRoutingKey.java @@ -1,9 +1,6 @@ package io.github.tcdl.msb.examples; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.MsbContext; -import io.github.tcdl.msb.api.MsbContextBuilder; -import io.github.tcdl.msb.api.RequestOptions; +import io.github.tcdl.msb.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,7 +13,7 @@ import java.util.concurrent.atomic.AtomicInteger; /** - * Run this consumer in conjunction with {@link ConsumerWithRoutingKeys} to see that only messages sent with + * Run this producer in conjunction with {@link ConsumerWithRoutingKeys} to see that only messages sent with * routing keys 'zero' and 'two' are consumed and the messages sent with routing key 'one' are not. */ public class ProducerWithRoutingKey { @@ -40,7 +37,8 @@ public static void main(String[] args) { Runnable task = () -> { String routingKey = tikToRoutingKey.get(tik.getAndIncrement() % 3); - RequestOptions requestOptions = new RequestOptions.Builder() + RequestOptions requestOptions = new AmqpRequestOptions.Builder() + .withExchangeType(ExchangeType.TOPIC) .withRoutingKey(routingKey) .withMessageTemplate(messageTemplate) .build(); diff --git a/jmeter/pom.xml b/jmeter/pom.xml index 1a78488f..23f54a57 100644 --- a/jmeter/pom.xml +++ b/jmeter/pom.xml @@ -1,14 +1,12 @@ - + 4.0.0 io.github.tcdl.msb msb-java - 1.5.2-SNAPSHOT + 1.5.3-SNAPSHOT ../pom.xml diff --git a/msb-spring-boot-starter/src/main/resources/META-INF/spring.factories b/msb-spring-boot-starter/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 4bd0214c..00000000 --- a/msb-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.github.tcdl.msb.MsbContextAutoConfiguration,\ -io.github.tcdl.msb.MsbConfigAutoConfiguration \ No newline at end of file diff --git a/msb-spring-boot-starter/src/main/resources/defaultMsbConfig.conf b/msb-spring-boot-starter/src/main/resources/defaultMsbConfig.conf deleted file mode 100644 index bd872236..00000000 --- a/msb-spring-boot-starter/src/main/resources/defaultMsbConfig.conf +++ /dev/null @@ -1,35 +0,0 @@ -msbConfig { - - # Service Details - serviceDetails = { - name = ${?SERVICE_NAME} - - instanceId = ${?SERVICE_INSTANCE_ID} - - version = "1.0.0" - } - - brokerAdapterFactory = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory" - - # Thread pool used for scheduling ack and response timeout tasks - timerThreadPoolSize = 2 - - threadingConfig = { - consumerThreadPoolSize = 5 - consumerThreadPoolQueueCapacity = -1 - } - - # Enable/disable message validation against json schema - validateMessage = false - - # Broker Adapter Defaults - brokerConfig = { - # host = "127.0.0.1" - # port = "5672" - # username = "user" - # password = "p@ssw0rd" - # virtualHost = "" - # useSSL = "false" - durable = true - } -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2827a39f..c669899d 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.5.2-SNAPSHOT + 1.5.3-SNAPSHOT msb java msb java pom @@ -35,7 +35,7 @@ acceptance jmeter examples - msb-spring-boot-starter + spring-boot-starter diff --git a/release-notes.html b/release-notes.html index 81831068..29a6acd4 100644 --- a/release-notes.html +++ b/release-notes.html @@ -4,9 +4,9 @@ -

Welcome to MSB-Java version 1.5.1

+

Welcome to MSB-Java version 1.5.2

-

July 29, 2016

+

October 3, 2016

 Features of MSB-Java version 1.5.2:
diff --git a/msb-spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
similarity index 58%
rename from msb-spring-boot-starter/pom.xml
rename to spring-boot-starter/pom.xml
index 42f13918..cb5b3e44 100644
--- a/msb-spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -1,11 +1,15 @@
 
 
+	
+		io.github.tcdl.msb
+		msb-java
+		1.5.3-SNAPSHOT
+		../pom.xml
+	
 	4.0.0
-	io.github.tcdl.msb
 	msb-spring-boot-starter
 	msb spring boot starter
-	0.0.1-SNAPSHOT
 	jar
 
 	
@@ -14,8 +18,6 @@
 
 		1.8
 		1.8
-
-		1.5.2-SNAPSHOT 
 	
 
 	
@@ -24,28 +26,49 @@
 			spring-boot-autoconfigure
 		
 
+		
+			org.springframework.boot
+			spring-boot-starter-test
+			test
+		
+
 		
 			io.github.tcdl.msb
 			msb-java-core
-			${msb.version}
+			${project.version}
 		
 
 		
 			io.github.tcdl.msb
 			msb-java-amqp
-			${msb.version}
+			${project.version}
 		
 
+        
+        
+            org.slf4j
+            jcl-over-slf4j
+            1.7.12
+            test
+        
+
+        
+        
+            ch.qos.logback
+            logback-core
+            1.1.3
+            test
+        
+
 		
 		
 			org.springframework.boot
 			spring-boot-configuration-processor
-			true
 		
 	
 
 	
-		 
+		
 			
 				org.springframework.boot
 				spring-boot-dependencies
@@ -56,25 +79,4 @@
 		
 	
 
-	
-		
-			spring-milestones
-			Spring Milestones
-			https://repo.spring.io/milestone
-			
-				false
-			
-		
-	
-	
-		
-			spring-milestones
-			Spring Milestones
-			https://repo.spring.io/milestone
-			
-				false
-			
-		
-	
-
 
diff --git a/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbConfigAutoConfiguration.java b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java
similarity index 51%
rename from msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbConfigAutoConfiguration.java
rename to spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java
index 3122cc41..776fbe03 100644
--- a/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbConfigAutoConfiguration.java
+++ b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java
@@ -1,9 +1,10 @@
-package io.github.tcdl.msb;
+package io.github.tcdl.msb.autoconfigure;
 
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigValueFactory;
 import io.github.tcdl.msb.config.MsbConfig;
+import io.github.tcdl.msb.support.Utils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.AutoConfigureBefore;
@@ -16,43 +17,64 @@
 @EnableConfigurationProperties(MsbProperties.class)
 public class MsbConfigAutoConfiguration {
 
+    public static final String DEFAULT_VERSION = "1.0.0";
+    public static final String DEFAULT_APP_NAME = Utils.generateId();
+    public static final String DEFAULT_ADAPTER_FACTORY = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory";
+    private static final boolean DEFAULT_BROKER_DURABLE = true;
+    public static final int DEFAULT_TIMER_THREAD_POOL_SIZE = 2;
+
     @Autowired
     MsbProperties msbProperties;
 
     @Bean
     public MsbConfig config() {
-        Config config = ConfigFactory.load("defaultMsbConfig");
-        config = config.withValue("msbConfig.serviceDetails.name", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.name));
+        Config config = ConfigFactory.load("reference");
+        String appName = StringUtils.isNotBlank(msbProperties.serviceDetails.name) ? msbProperties.serviceDetails.name : DEFAULT_APP_NAME;
+        config = config.withValue("msbConfig.serviceDetails.name", ConfigValueFactory.fromAnyRef(appName));
         config = config.withValue("msbConfig.serviceDetails.instanceId", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.instanceId));
 
         // Service Details
-        if (StringUtils.isNoneBlank(msbProperties.serviceDetails.version))
-            config = config.withValue("msbConfig.serviceDetails.version", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.version));
+        String version = StringUtils.isNotBlank(msbProperties.serviceDetails.version) ? msbProperties.serviceDetails.version : DEFAULT_VERSION;
+        config = config.withValue("msbConfig.serviceDetails.version", ConfigValueFactory.fromAnyRef(version));
         if (StringUtils.isNoneBlank(msbProperties.serviceDetails.hostname))
-            config = config.withValue("msbConfig.serviceDetails.version", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.hostname));
+            config = config.withValue("msbConfig.serviceDetails.hostname", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.hostname));
         if (StringUtils.isNoneBlank(msbProperties.serviceDetails.ip))
             config = config.withValue("msbConfig.serviceDetails.ip", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.ip));
         if (msbProperties.serviceDetails.pid != null)
             config = config.withValue("msbConfig.serviceDetails.pid", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.pid));
 
         // Broker Adapter Factory
-        if (StringUtils.isNotBlank(msbProperties.brokerAdapterFactory))
-            config = config.withValue("msbConfig.brokerAdapterFactory", ConfigValueFactory.fromAnyRef(msbProperties.brokerAdapterFactory));
+        String brokerAdapterFactory = StringUtils.isNotBlank(msbProperties.brokerAdapterFactory) ? msbProperties.brokerAdapterFactory : DEFAULT_ADAPTER_FACTORY;
+        config = config.withValue("msbConfig.brokerAdapterFactory", ConfigValueFactory.fromAnyRef(brokerAdapterFactory));
 
         // Thread pool used for scheduling ack and response timeout tasks
-        if (msbProperties.timerThreadPoolSize != null)
-            config = config.withValue("msbConfig.timerThreadPoolSize", ConfigValueFactory.fromAnyRef(msbProperties.timerThreadPoolSize));
+        Integer timerThreadPoolSize = msbProperties.timerThreadPoolSize != null ? msbProperties.timerThreadPoolSize : DEFAULT_TIMER_THREAD_POOL_SIZE;
+        config = config.withValue("msbConfig.timerThreadPoolSize", ConfigValueFactory.fromAnyRef(timerThreadPoolSize));
 
         // Threading Config for Clients
-        if (msbProperties.consumerThreadPoolSize != null)
-            config = config.withValue("msbConfig.threadingConfig.consumerThreadPoolSize", ConfigValueFactory.fromAnyRef(msbProperties.consumerThreadPoolSize));
-        if (msbProperties.consumerThreadPoolQueueCapacity != null)
-            config = config.withValue("msbConfig.threadingConfig.consumerThreadPoolQueueCapacity", ConfigValueFactory.fromAnyRef(msbProperties.consumerThreadPoolQueueCapacity));
+        if (msbProperties.threadingConfig.consumerThreadPoolSize != null)
+            config = config.withValue("msbConfig.threadingConfig.consumerThreadPoolSize", ConfigValueFactory.fromAnyRef(msbProperties.threadingConfig.consumerThreadPoolSize));
+        if (msbProperties.threadingConfig.consumerThreadPoolQueueCapacity != null)
+            config = config.withValue("msbConfig.threadingConfig.consumerThreadPoolQueueCapacity", ConfigValueFactory.fromAnyRef(msbProperties.threadingConfig.consumerThreadPoolQueueCapacity));
 
         // Enable/disable message validation against json schema
         if (msbProperties.validateMessage != null)
             config = config.withValue("msbConfig.validateMessage", ConfigValueFactory.fromAnyRef(msbProperties.validateMessage));
 
+        //MDC logging
+        if (msbProperties.mdcLogging.enabled != null)
+            config = config.withValue("msbConfig.mdcLogging.enabled", ConfigValueFactory.fromAnyRef(msbProperties.mdcLogging.enabled));
+        if (StringUtils.isNotBlank(msbProperties.mdcLogging.splitTagsBy))
+            config = config.withValue("msbConfig.mdcLogging.splitTagsBy", ConfigValueFactory.fromAnyRef(msbProperties.mdcLogging.splitTagsBy));
+        if (StringUtils.isNotBlank(msbProperties.mdcLogging.messageKeys.messageTags))
+            config = config.withValue("msbConfig.mdcLogging.messageKeys.messageTags", ConfigValueFactory.fromAnyRef(msbProperties.mdcLogging.messageKeys.messageTags));
+        if (StringUtils.isNotBlank(msbProperties.mdcLogging.messageKeys.correlationId))
+            config = config.withValue("msbConfig.mdcLogging.messageKeys.correlationId", ConfigValueFactory.fromAnyRef(msbProperties.mdcLogging.messageKeys.correlationId));
+
+        //requestOptions
+        if (msbProperties.requestOptions.responseTimeout != null)
+            config = config.withValue("msbConfig.requestOptions.responseTimeout", ConfigValueFactory.fromAnyRef(msbProperties.requestOptions.responseTimeout));
+
         //Broker Adapter Defaults
         if (StringUtils.isNotBlank(msbProperties.brokerConfig.host))
             config = config.withValue("msbConfig.brokerConfig.host", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.host));
@@ -60,12 +82,14 @@ public MsbConfig config() {
             config = config.withValue("msbConfig.brokerConfig.port", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.port));
         if (StringUtils.isNotBlank(msbProperties.brokerConfig.userName))
             config = config.withValue("msbConfig.brokerConfig.username", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.userName));
+        if (StringUtils.isNotBlank(msbProperties.brokerConfig.password))
+            config = config.withValue("msbConfig.brokerConfig.password", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.password));
         if (StringUtils.isNotBlank(msbProperties.brokerConfig.virtualHost))
             config = config.withValue("msbConfig.brokerConfig.virtualHost", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.virtualHost));
         if (msbProperties.brokerConfig.useSSL != null)
             config = config.withValue("msbConfig.brokerConfig.useSSL", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.useSSL));
-        if (msbProperties.brokerConfig.durable != null)
-            config = config.withValue("msbConfig.brokerConfig.durable", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.durable));
+        boolean durable = msbProperties.brokerConfig.durable != null ? msbProperties.brokerConfig.durable : DEFAULT_BROKER_DURABLE;
+        config = config.withValue("msbConfig.brokerConfig.durable", ConfigValueFactory.fromAnyRef(durable));
         if (msbProperties.brokerConfig.charset != null)
             config = config.withValue("msbConfig.brokerConfig.charset", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.charset));
         if (StringUtils.isNotBlank(msbProperties.brokerConfig.groupId))
@@ -74,6 +98,10 @@ public MsbConfig config() {
             config = config.withValue("msbConfig.brokerConfig.heartbeatIntervalSec", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.heartbeatIntervalSec));
         if (msbProperties.brokerConfig.networkRecoveryIntervalMs != null)
             config = config.withValue("msbConfig.brokerConfig.networkRecoveryIntervalMs", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.networkRecoveryIntervalMs));
+        if (StringUtils.isNotBlank(msbProperties.brokerConfig.defaultExchangeType))
+            config = config.withValue("msbConfig.brokerConfig.defaultExchangeType", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.defaultExchangeType));
+        if (msbProperties.brokerConfig.prefetchCount != null)
+            config = config.withValue("msbConfig.brokerConfig.prefetchCount", ConfigValueFactory.fromAnyRef(msbProperties.brokerConfig.prefetchCount));
 
         return new MsbConfig(config);
     }
diff --git a/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbContextAutoConfiguration.java b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java
similarity index 96%
rename from msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbContextAutoConfiguration.java
rename to spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java
index 9d890966..782c26b4 100644
--- a/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbContextAutoConfiguration.java
+++ b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java
@@ -1,4 +1,4 @@
-package io.github.tcdl.msb;
+package io.github.tcdl.msb.autoconfigure;
 
 import io.github.tcdl.msb.api.MessageTemplate;
 import io.github.tcdl.msb.api.MsbContext;
diff --git a/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbProperties.java b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbProperties.java
similarity index 58%
rename from msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbProperties.java
rename to spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbProperties.java
index 8fcb171e..f39d3500 100644
--- a/msb-spring-boot-starter/src/main/java/io/github/tcdl/msb/MsbProperties.java
+++ b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbProperties.java
@@ -1,4 +1,4 @@
-package io.github.tcdl.msb;
+package io.github.tcdl.msb.autoconfigure;
 
 import org.springframework.boot.context.properties.ConfigurationProperties;
 
@@ -11,9 +11,10 @@ public class MsbProperties {
     String brokerAdapterFactory;
     Integer timerThreadPoolSize;
     Boolean validateMessage;
-    Integer consumerThreadPoolSize;
-    Integer consumerThreadPoolQueueCapacity;
+    ThreadingConfig threadingConfig = new ThreadingConfig();
     BrokerConfig brokerConfig = new BrokerConfig();
+    MdcLogging mdcLogging = new MdcLogging();
+    RequestOptions requestOptions = new RequestOptions();
 
     public ServiceDetails getServiceDetails() {
         return serviceDetails;
@@ -47,28 +48,57 @@ public void setValidateMessage(Boolean validateMessage) {
         this.validateMessage = validateMessage;
     }
 
-    public Integer getConsumerThreadPoolSize() {
-        return consumerThreadPoolSize;
+    public BrokerConfig getBrokerConfig() {
+        return brokerConfig;
     }
 
-    public void setConsumerThreadPoolSize(Integer consumerThreadPoolSize) {
-        this.consumerThreadPoolSize = consumerThreadPoolSize;
+    public void setBrokerConfig(BrokerConfig brokerConfig) {
+        this.brokerConfig = brokerConfig;
     }
 
-    public Integer getConsumerThreadPoolQueueCapacity() {
-        return consumerThreadPoolQueueCapacity;
+    public ThreadingConfig getThreadingConfig() {
+        return threadingConfig;
     }
 
-    public void setConsumerThreadPoolQueueCapacity(Integer consumerThreadPoolQueueCapacity) {
-        this.consumerThreadPoolQueueCapacity = consumerThreadPoolQueueCapacity;
+    public void setThreadingConfig(ThreadingConfig threadingConfig) {
+        this.threadingConfig = threadingConfig;
     }
 
-    public BrokerConfig getBrokerConfig() {
-        return brokerConfig;
+    public MdcLogging getMdcLogging() {
+        return mdcLogging;
     }
 
-    public void setBrokerConfig(BrokerConfig brokerConfig) {
-        this.brokerConfig = brokerConfig;
+    public void setMdcLogging(MdcLogging mdcLogging) {
+        this.mdcLogging = mdcLogging;
+    }
+
+    public RequestOptions getRequestOptions() {
+        return requestOptions;
+    }
+
+    public void setRequestOptions(RequestOptions requestOptions) {
+        this.requestOptions = requestOptions;
+    }
+
+    public class ThreadingConfig {
+        Integer consumerThreadPoolSize;
+        Integer consumerThreadPoolQueueCapacity;
+
+        public void setConsumerThreadPoolSize(Integer consumerThreadPoolSize) {
+            this.consumerThreadPoolSize = consumerThreadPoolSize;
+        }
+
+        public Integer getConsumerThreadPoolSize() {
+            return consumerThreadPoolSize;
+        }
+
+        public Integer getConsumerThreadPoolQueueCapacity() {
+            return consumerThreadPoolQueueCapacity;
+        }
+
+        public void setConsumerThreadPoolQueueCapacity(Integer consumerThreadPoolQueueCapacity) {
+            this.consumerThreadPoolQueueCapacity = consumerThreadPoolQueueCapacity;
+        }
     }
 
     public class ServiceDetails {
@@ -128,6 +158,69 @@ public void setPid(Long pid) {
         }
     }
 
+    public class MdcLogging {
+        Boolean enabled;
+        String splitTagsBy;
+        MessageKeys messageKeys = new MessageKeys();
+
+        public Boolean getEnabled() {
+            return enabled;
+        }
+
+        public void setEnabled(Boolean enabled) {
+            this.enabled = enabled;
+        }
+
+        public String getSplitTagsBy() {
+            return splitTagsBy;
+        }
+
+        public void setSplitTagsBy(String splitTagsBy) {
+            this.splitTagsBy = splitTagsBy;
+        }
+
+        public MessageKeys getMessageKeys() {
+            return messageKeys;
+        }
+
+        public void setMessageKeys(MessageKeys messageKeys) {
+            this.messageKeys = messageKeys;
+        }
+    }
+
+    public class MessageKeys {
+        String messageTags;
+        String correlationId;
+
+        public String getMessageTags() {
+            return messageTags;
+        }
+
+        public void setMessageTags(String messageTags) {
+            this.messageTags = messageTags;
+        }
+
+        public String getCorrelationId() {
+            return correlationId;
+        }
+
+        public void setCorrelationId(String correlationId) {
+            this.correlationId = correlationId;
+        }
+    }
+
+    public class RequestOptions {
+        Integer responseTimeout;
+
+        public Integer getResponseTimeout() {
+            return responseTimeout;
+        }
+
+        public void setResponseTimeout(Integer responseTimeout) {
+            this.responseTimeout = responseTimeout;
+        }
+    }
+
     public class BrokerConfig {
         Charset charset;
         String host;
@@ -138,8 +231,10 @@ public class BrokerConfig {
         Boolean useSSL;
         String groupId;
         Boolean durable;
+        String defaultExchangeType;
         Integer heartbeatIntervalSec;
         Long networkRecoveryIntervalMs;
+        Integer prefetchCount;
 
         public Charset getCharset() {
             return charset;
@@ -228,6 +323,22 @@ public Long getNetworkRecoveryIntervalMs() {
         public void setNetworkRecoveryIntervalMs(Long networkRecoveryIntervalMs) {
             this.networkRecoveryIntervalMs = networkRecoveryIntervalMs;
         }
+
+        public String getDefaultExchangeType() {
+            return defaultExchangeType;
+        }
+
+        public void setDefaultExchangeType(String defaultExchangeType) {
+            this.defaultExchangeType = defaultExchangeType;
+        }
+
+        public Integer getPrefetchCount() {
+            return prefetchCount;
+        }
+
+        public void setPrefetchCount(Integer prefetchCount) {
+            this.prefetchCount = prefetchCount;
+        }
     }
 
 }
diff --git a/spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..29c77b83
--- /dev/null
+++ b/spring-boot-starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.github.tcdl.msb.autoconfigure.MsbContextAutoConfiguration,\
+io.github.tcdl.msb.autoconfigure.MsbConfigAutoConfiguration
\ No newline at end of file
diff --git a/spring-boot-starter/src/test/java/io/github/tcdl/msb/autoconfigure/MsbAutoConfigurationTest.java b/spring-boot-starter/src/test/java/io/github/tcdl/msb/autoconfigure/MsbAutoConfigurationTest.java
new file mode 100644
index 00000000..d8e48dc5
--- /dev/null
+++ b/spring-boot-starter/src/test/java/io/github/tcdl/msb/autoconfigure/MsbAutoConfigurationTest.java
@@ -0,0 +1,101 @@
+package io.github.tcdl.msb.autoconfigure;
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import io.github.tcdl.msb.api.MessageTemplate;
+import io.github.tcdl.msb.api.MsbContext;
+import io.github.tcdl.msb.config.MsbConfig;
+import org.junit.After;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.boot.test.util.EnvironmentTestUtils;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class MsbAutoConfigurationTest {
+
+    private static final Integer TTL = 1000000;
+    private AnnotationConfigApplicationContext context;
+
+    @After
+    public void tearDown() {
+        if (this.context != null) {
+            this.context.close();
+        }
+    }
+
+    @Test
+    public void testDefaultMsbConfig() {
+        Config config = ConfigFactory.load("reference.conf");
+        load(EmptyConfiguration.class);
+        MsbConfig msbConfig = this.context.getBean(MsbConfig.class);
+
+        assertNotNull(msbConfig);
+        assertNotNull(msbConfig.getServiceDetails().getName());
+        assertEquals(config.getString("msbConfig.brokerAdapterFactory"), msbConfig.getBrokerAdapterFactory());
+        assertEquals(MsbConfigAutoConfiguration.DEFAULT_TIMER_THREAD_POOL_SIZE, msbConfig.getTimerThreadPoolSize());
+        assertEquals(config.getInt("msbConfig.threadingConfig.consumerThreadPoolSize"), msbConfig.getConsumerThreadPoolSize());
+        assertEquals(config.getInt("msbConfig.threadingConfig.consumerThreadPoolQueueCapacity"), msbConfig.getConsumerThreadPoolQueueCapacity());
+        assertEquals(config.getBoolean("msbConfig.validateMessage"), msbConfig.isValidateMessage());
+    }
+
+    @Test
+    public void testOverrideMsbConfigParams() {
+        load(EmptyConfiguration.class, "msbConfig.serviceDetails.name=test-name", "msbConfig.threadingConfig.consumerThreadPoolSize=100", "msbConfig.brokerConfig.host=192.168.0.1");
+        MsbConfig msbConfig = this.context.getBean(MsbConfig.class);
+
+        assertEquals("test-name", msbConfig.getServiceDetails().getName());
+        assertEquals(100, msbConfig.getConsumerThreadPoolSize());
+        assertEquals("192.168.0.1", msbConfig.getBrokerConfig().getString("host"));
+    }
+
+    @Test
+    public void testDefaultMessageTemplate() {
+        load(EmptyConfiguration.class);
+        MessageTemplate messageTemplate = this.context.getBean(MessageTemplate.class);
+
+        assertNotNull(messageTemplate);
+    }
+
+    @Test
+    public void testOverrideMessageTemplate() {
+        load(ConfigurationWithCustomMessageTemplate.class);
+        MessageTemplate messageTemplate = this.context.getBean(MessageTemplate.class);
+
+        assertNotNull(messageTemplate);
+        assertEquals(TTL, messageTemplate.getTtl());
+    }
+
+
+
+
+
+    static class ConfigurationWithCustomMessageTemplate extends EmptyConfiguration{
+        @Bean
+        MessageTemplate messageTemplate() {
+            return MessageTemplate.copyOf(new MessageTemplate()).withTtl(TTL);
+        }
+    }
+
+    @Configuration
+    static class EmptyConfiguration {
+        //override bean to prevent establishing connection to real message queue
+        @Bean
+        MsbContext msbContext(){
+            return Mockito.mock(MsbContext.class);
+        }
+    }
+
+    private void load(Class config, String... environment) {
+        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
+        EnvironmentTestUtils.addEnvironment(applicationContext, environment);
+        applicationContext.register(config);
+        applicationContext.register(MsbConfigAutoConfiguration.class, MsbContextAutoConfiguration.class);
+        applicationContext.refresh();
+        this.context = applicationContext;
+    }
+}

From 858ac6d4c771620f3902097a39e45f86b603bb8b Mon Sep 17 00:00:00 2001
From: Nikita Galkin 
Date: Tue, 1 Nov 2016 13:16:57 +0200
Subject: [PATCH 161/226] Update env variables doc, default values and naming

---
 amqp/src/main/resources/amqp.conf |  5 ++++-
 doc/MSB.md                        | 11 +++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf
index 725e2f4e..f5e578e1 100644
--- a/amqp/src/main/resources/amqp.conf
+++ b/amqp/src/main/resources/amqp.conf
@@ -7,9 +7,12 @@ config.amqp = {
   host = ${?MSB_BROKER_HOST}
   port = "5672"
   port = ${?MSB_BROKER_PORT}
+  username = "guest"
   username = ${?MSB_BROKER_USER_NAME}
+  password = "guest"
   password = ${?MSB_BROKER_PASSWORD}
-  virtualHost = ${?MSB_BROKER_VIRTUAL_HOST}
+  virtualHost = "/"
+  virtualHost = ${?MSB_BROKER_AMQP_VHOST}
   useSSL = false # true / false
   useSSL = ${?MSB_BROKER_USE_SSL}
 
diff --git a/doc/MSB.md b/doc/MSB.md
index c0be5835..cbc346e7 100644
--- a/doc/MSB.md
+++ b/doc/MSB.md
@@ -320,6 +320,17 @@ Here, the override field `name = ${?MSB_SERVICE_NAME}` simply vanishes if there'
 
 `brokerAdapterFactory` – message broker class. Defaults to `"io.github.tcdl.adapters.amqp.AmqpAdapterFactory"`.
 
+### Environment Variables
+
+- MSB_SERVICE_NAME
+- MSB_SERVICE_VERSION
+- MSB_SERVICE_INSTANCE_ID
+- MSB_BROKER_HOST, default "127.0.0.1".
+- MSB_BROKER_PORT, default 5672.
+- MSB_BROKER_USER_NAME, default "guest".
+- MSB_BROKER_PASSWORD, default "guest".
+- MSB_BROKER_VIRTUAL_HOST, default "/".
+
 ### Mapped Diagnostic Context settings
 This section provides settings for Mapped Diagnostic Context logging that gives a possibility to save some parameters of the incoming messages into a thread-local storage so it would be easier to track message processing.
 The section `mdcLogging`:

From 612903bf40d140e67909243adc603233357dcd59 Mon Sep 17 00:00:00 2001
From: Nikita Galkin 
Date: Thu, 3 Nov 2016 09:39:01 +0200
Subject: [PATCH 162/226] Update env variables doc, default values and naming

---
 amqp/src/main/resources/amqp.conf | 2 ++
 doc/MSB.md                        | 1 +
 2 files changed, 3 insertions(+)

diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf
index f5e578e1..9c1c44fe 100644
--- a/amqp/src/main/resources/amqp.conf
+++ b/amqp/src/main/resources/amqp.conf
@@ -12,6 +12,8 @@ config.amqp = {
   password = "guest"
   password = ${?MSB_BROKER_PASSWORD}
   virtualHost = "/"
+  # For backwards compatibility. Deprecated. todo: remove in new version.
+  virtualHost = ${?MSB_BROKER_VIRTUAL_HOST}
   virtualHost = ${?MSB_BROKER_AMQP_VHOST}
   useSSL = false # true / false
   useSSL = ${?MSB_BROKER_USE_SSL}
diff --git a/doc/MSB.md b/doc/MSB.md
index cbc346e7..c917bc60 100644
--- a/doc/MSB.md
+++ b/doc/MSB.md
@@ -330,6 +330,7 @@ Here, the override field `name = ${?MSB_SERVICE_NAME}` simply vanishes if there'
 - MSB_BROKER_USER_NAME, default "guest".
 - MSB_BROKER_PASSWORD, default "guest".
 - MSB_BROKER_VIRTUAL_HOST, default "/".
+- MSB_BROKER_USE_SSL, default false.
 
 ### Mapped Diagnostic Context settings
 This section provides settings for Mapped Diagnostic Context logging that gives a possibility to save some parameters of the incoming messages into a thread-local storage so it would be easier to track message processing.

From 98bcde9c80ba024fa7c6ee5934b51dd8d6d9bedf Mon Sep 17 00:00:00 2001
From: sergiv83 
Date: Fri, 4 Nov 2016 12:58:56 +0200
Subject: [PATCH 163/226] fix typo

---
 .../tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java
index 776fbe03..b4f9c526 100644
--- a/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java
+++ b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java
@@ -36,9 +36,9 @@ public MsbConfig config() {
         // Service Details
         String version = StringUtils.isNotBlank(msbProperties.serviceDetails.version) ? msbProperties.serviceDetails.version : DEFAULT_VERSION;
         config = config.withValue("msbConfig.serviceDetails.version", ConfigValueFactory.fromAnyRef(version));
-        if (StringUtils.isNoneBlank(msbProperties.serviceDetails.hostname))
+        if (StringUtils.isNotBlank(msbProperties.serviceDetails.hostname))
             config = config.withValue("msbConfig.serviceDetails.hostname", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.hostname));
-        if (StringUtils.isNoneBlank(msbProperties.serviceDetails.ip))
+        if (StringUtils.isNotBlank(msbProperties.serviceDetails.ip))
             config = config.withValue("msbConfig.serviceDetails.ip", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.ip));
         if (msbProperties.serviceDetails.pid != null)
             config = config.withValue("msbConfig.serviceDetails.pid", ConfigValueFactory.fromAnyRef(msbProperties.serviceDetails.pid));

From 72a962d73c5cdf819ed0bf4da3576206c2f507e6 Mon Sep 17 00:00:00 2001
From: alex 
Date: Tue, 8 Nov 2016 13:57:22 +0200
Subject: [PATCH 164/226] documentation update

---
 doc/MSB.md | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/doc/MSB.md b/doc/MSB.md
index cbc346e7..6cb58815 100644
--- a/doc/MSB.md
+++ b/doc/MSB.md
@@ -292,9 +292,9 @@ All configuration files use _key-value pair_ structure.
 ### Description of MSB configuration fields
 Service details section describes microservice parameters.
 
-- `name ` – microservice name. All running instances of the same microservice must have the same name.
-- `version` – microservice version. At the moment MSB-Java doesn't handle the value.
-- `instanceId` – unique microservice instance id. All running instances of the same microservice must have different instanceId.
+- `name ` – microservice name. All running instances of the same microservice must have the same name. Mandatory, has no default value.
+- `version` – microservice version. At the moment MSB-Java doesn't handle the value. Mandatory, has no default value.
+- `instanceId` – unique microservice instance id. All running instances of the same microservice must have different instanceId. If it is not set explicitly than it is set to random UUID at startup. Value is not preserved on restart.
 
 Additional instances of a microservice can help with load balancing. The simplest way to have multiple instances is to deploy, configure and run a microservice from different locations with different instanceId values. In this case a value of a instanceId need to be specified directly (or by means of environment variables, see below) in the application.conf, for example:
 
@@ -322,9 +322,9 @@ Here, the override field `name = ${?MSB_SERVICE_NAME}` simply vanishes if there'
 
 ### Environment Variables
 
-- MSB_SERVICE_NAME
-- MSB_SERVICE_VERSION
-- MSB_SERVICE_INSTANCE_ID
+- MSB_SERVICE_NAME, mandatory
+- MSB_SERVICE_VERSION, mandatory
+- MSB_SERVICE_INSTANCE_ID, default random UUID (generated on each startup)
 - MSB_BROKER_HOST, default "127.0.0.1".
 - MSB_BROKER_PORT, default 5672.
 - MSB_BROKER_USER_NAME, default "guest".
@@ -372,7 +372,7 @@ The section `brokerConfig` from [reference.conf](/core/src/main/resources/refere
 
 `port` – port number
 
-`useSSL` – defines whether we need to use SSL for AMQP connection. For the moment only simplest form of security is implemented that provides encryption but does not check remote certificates.
+`useSSL` – defines whether we need to use SSL for AMQP connection. For the moment only simplest form of security is implemented that provides encryption but does not check remote certificates. Is set fo `false` by default.
 
 `groupId` – microservices with the same `groupId` subscribed to the same namespace will receive messages from that namespace in round-robin fashion. If microservices have different `groupId`s and subscribed to the same namespace then all of those microservices are going to receive a copy of a message from that namespace.
 

From 00a52720ffa95f5b15fcde11b699c5925ef85d63 Mon Sep 17 00:00:00 2001
From: alex 
Date: Thu, 10 Nov 2016 15:26:23 +0200
Subject: [PATCH 165/226] ChanelMonitorAgent and ChannelMonitorAggregator
 removed

---
 acceptance/pom.xml                            |   2 +-
 amqp/pom.xml                                  |   2 +-
 cli/pom.xml                                   |   2 +-
 core/pom.xml                                  |   2 +-
 .../io/github/tcdl/msb/ChannelManager.java    |  37 +--
 .../java/io/github/tcdl/msb/Consumer.java     |   9 +-
 .../java/io/github/tcdl/msb/Producer.java     |   6 +-
 .../tcdl/msb/api/MsbContextBuilder.java       |  16 -
 .../io/github/tcdl/msb/api/ObjectFactory.java |  15 -
 .../tcdl/msb/api/monitor/AggregatorStats.java |  40 ---
 .../msb/api/monitor/AggregatorTopicStats.java | 106 ------
 .../api/monitor/ChannelMonitorAggregator.java |  51 ---
 .../github/tcdl/msb/impl/MsbContextImpl.java  |   1 -
 .../tcdl/msb/impl/ObjectFactoryImpl.java      |  44 ---
 .../msb/monitor/agent/AgentTopicStats.java    | 106 ------
 .../monitor/agent/ChannelMonitorAgent.java    |  48 ---
 .../agent/DefaultChannelMonitorAgent.java     | 154 ---------
 .../agent/NoopChannelMonitorAgent.java        |  31 --
 .../DefaultChannelMonitorAggregator.java      | 163 ----------
 .../msb/monitor/aggregator/HeartbeatTask.java |  56 ----
 .../msb/ChannelManagerConcurrentTest.java     |  93 +-----
 .../github/tcdl/msb/ChannelManagerTest.java   |  77 +----
 .../java/io/github/tcdl/msb/ConsumerTest.java |  79 ++---
 .../java/io/github/tcdl/msb/ProducerTest.java |  28 +-
 .../github/tcdl/msb/api/ChannelMonitorIT.java | 189 -----------
 .../tcdl/msb/impl/MsbContextImplTest.java     |   1 -
 .../tcdl/msb/impl/ObjectFactoryImplTest.java  |  39 +--
 .../msb/impl/PayloadConverterImplTest.java    |   1 -
 .../adapterfactory/TestMsbAdapterFactory.java |   3 +-
 .../objectfactory/TestMsbObjectFactory.java   |  14 -
 .../agent/DefaultChannelMonitorAgentTest.java | 160 ---------
 .../DefaultChannelMonitorAggregatorTest.java  | 304 ------------------
 .../monitor/aggregator/HeartbeatTaskTest.java |  86 -----
 examples/pom.xml                              |   2 +-
 .../tcdl/msb/examples/DateExtractor.java      |   1 -
 .../tcdl/msb/examples/FacetsAggregator.java   |   1 -
 .../io/github/tcdl/msb/examples/Monitor.java  |  29 --
 jmeter/pom.xml                                |   2 +-
 pom.xml                                       |   2 +-
 spring-boot-starter/pom.xml                   |   2 +-
 .../MsbContextAutoConfiguration.java          |   1 -
 41 files changed, 90 insertions(+), 1915 deletions(-)
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/api/monitor/AggregatorStats.java
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/api/monitor/AggregatorTopicStats.java
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/api/monitor/ChannelMonitorAggregator.java
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/monitor/agent/AgentTopicStats.java
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/monitor/agent/ChannelMonitorAgent.java
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/monitor/agent/NoopChannelMonitorAgent.java
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTask.java
 delete mode 100644 core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java
 delete mode 100644 core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java
 delete mode 100644 core/src/test/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregatorTest.java
 delete mode 100644 core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java
 delete mode 100644 examples/src/main/java/io/github/tcdl/msb/examples/Monitor.java

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 3e4dfe74..ddf56692 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.3-SNAPSHOT
+        1.6.0-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 564e365a..ef791945 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.3-SNAPSHOT
+        1.6.0-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/cli/pom.xml b/cli/pom.xml
index eaebce95..75154bd1 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.3-SNAPSHOT
+        1.6.0-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/core/pom.xml b/core/pom.xml
index e62043bf..205ed0be 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.5.3-SNAPSHOT
+        1.6.0-SNAPSHOT
         ../pom.xml
     
     4.0.0
diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
index 32b227c2..a5b59e36 100644
--- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
@@ -1,28 +1,26 @@
 package io.github.tcdl.msb;
 
-import java.time.Clock;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
 import com.fasterxml.jackson.databind.ObjectMapper;
-import io.github.tcdl.msb.adapters.*;
-import io.github.tcdl.msb.api.Callback;
+import io.github.tcdl.msb.adapters.AdapterFactory;
+import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.adapters.ProducerAdapter;
 import io.github.tcdl.msb.api.RequestOptions;
 import io.github.tcdl.msb.api.ResponderOptions;
 import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException;
 import io.github.tcdl.msb.collector.CollectorManager;
 import io.github.tcdl.msb.config.MsbConfig;
-import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.impl.SimpleMessageHandlerResolverImpl;
-import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent;
-import io.github.tcdl.msb.monitor.agent.NoopChannelMonitorAgent;
-import io.github.tcdl.msb.threading.MessageHandlerInvoker;
 import io.github.tcdl.msb.support.JsonValidator;
 import io.github.tcdl.msb.support.Utils;
+import io.github.tcdl.msb.threading.MessageHandlerInvoker;
 import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.time.Clock;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
 /**
  * {@link ChannelManager} creates consumers or producers on demand and manages them.
  */
@@ -37,7 +35,6 @@ public class ChannelManager {
     private final ObjectMapper messageMapper;
     private final AdapterFactory adapterFactory;
     private final MessageHandlerInvoker messageHandlerInvoker;
-    private ChannelMonitorAgent channelMonitorAgent;
 
     private final Map producersByTopic;
     private final Map consumersByTopic;
@@ -52,8 +49,6 @@ public ChannelManager(MsbConfig msbConfig, Clock clock, JsonValidator validator,
 
         this.producersByTopic = new ConcurrentHashMap<>();
         this.consumersByTopic = new ConcurrentHashMap<>();
-
-        channelMonitorAgent = new NoopChannelMonitorAgent();
     }
 
     public Producer findOrCreateProducer(String topic, RequestOptions requestOptions) {
@@ -62,7 +57,6 @@ public Producer findOrCreateProducer(String topic, RequestOptions requestOptions
 
         Producer producer = producersByTopic.computeIfAbsent(topic, key -> {
             Producer newProducer = createProducer(key, requestOptions);
-            channelMonitorAgent.producerTopicCreated(key);
             return newProducer;
         });
 
@@ -111,7 +105,7 @@ public synchronized void subscribeForResponses(String topic, CollectorManager co
      */
     public synchronized void unsubscribe(String topic) {
         if (consumersByTopic.get(topic) != null) {
-            stopConsumer(topic, consumersByTopic.remove(topic));
+            stopConsumer(consumersByTopic.remove(topic));
         }
     }
 
@@ -121,29 +115,26 @@ private void subscribe(String topic, boolean isResponseTopic, ResponderOptions r
         } else {
             Consumer newConsumer = createConsumer(topic, isResponseTopic, responderOptions, messageHandlerResolver);
             newConsumer.subscribe();
-            channelMonitorAgent.consumerTopicCreated(topic);
             consumersByTopic.put(topic, newConsumer);
         }
     }
 
-    private void stopConsumer(String topic, Consumer consumer) {
+    private void stopConsumer(Consumer consumer) {
         if (consumer != null) {
             consumer.end();
-            channelMonitorAgent.consumerTopicRemoved(topic);
         }
     }
 
     private Producer createProducer(String topic, RequestOptions requestOptions) {
         Utils.validateTopic(topic);
         ProducerAdapter adapter = this.adapterFactory.createProducerAdapter(topic, requestOptions);
-        Callback monitorAgentCallback = message -> channelMonitorAgent.producerMessageSent(topic);
-        return new Producer(adapter, topic, monitorAgentCallback, messageMapper);
+        return new Producer(adapter, topic, messageMapper);
     }
 
     private Consumer createConsumer(String topic, boolean isResponseTopic, ResponderOptions responderOptions, MessageHandlerResolver messageHandlerResolver) {
         Utils.validateTopic(topic);
         ConsumerAdapter adapter = this.adapterFactory.createConsumerAdapter(topic, responderOptions, isResponseTopic);
-        return new Consumer(adapter, messageHandlerInvoker, topic, messageHandlerResolver, msbConfig, clock, channelMonitorAgent, validator, messageMapper);
+        return new Consumer(adapter, messageHandlerInvoker, topic, messageHandlerResolver, msbConfig, clock, validator, messageMapper);
     }
 
     public void shutdown() {
@@ -152,8 +143,4 @@ public void shutdown() {
         messageHandlerInvoker.shutdown();
         LOG.info("Shutdown complete");
     }
-
-    public void setChannelMonitorAgent(ChannelMonitorAgent channelMonitorAgent) {
-        this.channelMonitorAgent = channelMonitorAgent;
-    }
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java
index ecd062c8..320bcea4 100644
--- a/core/src/main/java/io/github/tcdl/msb/Consumer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java
@@ -7,7 +7,6 @@
 import io.github.tcdl.msb.api.message.MetaMessage;
 import io.github.tcdl.msb.collector.ConsumedMessagesAwareMessageHandler;
 import io.github.tcdl.msb.config.MsbConfig;
-import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent;
 import io.github.tcdl.msb.support.JsonValidator;
 import io.github.tcdl.msb.support.Utils;
 
@@ -35,7 +34,6 @@ public class Consumer {
     private final MessageHandlerInvoker messageHandlerInvoker;
     private final String topic;
     private final MsbConfig msbConfig;
-    private final ChannelMonitorAgent channelMonitorAgent;
     private final Clock clock;
     private final MessageHandlerResolver messageHandlerResolver;
     private final JsonValidator validator;
@@ -49,13 +47,12 @@ public class Consumer {
      * @param messageHandlerResolver resolves {@link MessageHandler} instance that user can implement to handle received messages.
      * @param msbConfig consumer configs
      * @param clock
-     * @param channelMonitorAgent
      * @param validator validates incoming messages
      * @param messageMapper message deserializer
      */
     public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvoker messageHandlerInvoker,
             String topic, MessageHandlerResolver messageHandlerResolver, MsbConfig msbConfig,
-            Clock clock, ChannelMonitorAgent channelMonitorAgent, JsonValidator validator, ObjectMapper messageMapper) {
+            Clock clock, JsonValidator validator, ObjectMapper messageMapper) {
 
         LOG.debug("Creating consumer for topic: {}", topic);
         Validate.notNull(rawAdapter, "the 'rawAdapter' must not be null");
@@ -64,7 +61,6 @@ public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvoker messageHandler
         Validate.notNull(messageHandlerResolver, "the 'messageHandlerResolver' must not be null");
         Validate.notNull(msbConfig, "the 'msbConfig' must not be null");
         Validate.notNull(clock, "the 'clock' must not be null");
-        Validate.notNull(channelMonitorAgent, "the 'channelMonitorAgent' must not be null");
         Validate.notNull(validator, "the 'validator' must not be null");
         Validate.notNull(messageMapper, "the 'messageMapper' must not be null");
 
@@ -74,7 +70,6 @@ public Consumer(ConsumerAdapter rawAdapter, MessageHandlerInvoker messageHandler
         this.messageHandlerResolver = messageHandlerResolver;
         this.msbConfig = msbConfig;
         this.clock = clock;
-        this.channelMonitorAgent = channelMonitorAgent;
         this.validator = validator;
         this.messageMapper = messageMapper;
 
@@ -106,8 +101,6 @@ public void end() {
     protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerInternal acknowledgeHandler) {
         LOG.debug("{} message received [{}]", loggingTag, jsonMessage);
 
-        channelMonitorAgent.consumerMessageReceived(topic);
-
         Message message;
 
         try {
diff --git a/core/src/main/java/io/github/tcdl/msb/Producer.java b/core/src/main/java/io/github/tcdl/msb/Producer.java
index 5c0debc5..43cc6c0d 100644
--- a/core/src/main/java/io/github/tcdl/msb/Producer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Producer.java
@@ -20,18 +20,15 @@ public class Producer {
     private static final Logger LOG = LoggerFactory.getLogger(Producer.class);
 
     private final ProducerAdapter rawAdapter;
-    private final Callback messageHandler;
     private final ObjectMapper messageMapper;
 
-    public Producer(ProducerAdapter rawAdapter, String topic, Callback messageHandler, ObjectMapper messageMapper) {
+    public Producer(ProducerAdapter rawAdapter, String topic, ObjectMapper messageMapper) {
         LOG.debug("Creating producer for topic: {}", topic);
         Validate.notNull(rawAdapter, "the 'rawAdapter' must not be null");
         Validate.notNull(topic, "the 'topic' must not be null");
-        Validate.notNull(messageHandler, "the 'messageHandler' must not be null");
         Validate.notNull(messageMapper, "the 'messageMapper' must not be null");
 
         this.rawAdapter = rawAdapter;
-        this.messageHandler = messageHandler;
         this.messageMapper = messageMapper;
     }
 
@@ -41,7 +38,6 @@ public void publish(Message message) {
             String jsonMessage = Utils.toJson(message, messageMapper);
             LOG.debug("Publishing message to adapter : {}", jsonMessage);
             rawAdapter.publish(jsonMessage, routingKey != null ? routingKey : StringUtils.EMPTY);
-            messageHandler.call(message);
         } catch (ChannelException | JsonConversionException e) {
             LOG.error("Exception while message publish to adapter", e);
             throw e;
diff --git a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
index a92ed5db..1166ba66 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
@@ -18,7 +18,6 @@
 import io.github.tcdl.msb.impl.MsbContextImpl;
 import io.github.tcdl.msb.impl.ObjectFactoryImpl;
 import io.github.tcdl.msb.message.MessageFactory;
-import io.github.tcdl.msb.monitor.agent.DefaultChannelMonitorAgent;
 import io.github.tcdl.msb.support.JsonValidator;
 import io.github.tcdl.msb.threading.*;
 import org.slf4j.Logger;
@@ -37,7 +36,6 @@ public class MsbContextBuilder {
     private Config config;
     private MsbConfig msbConfig;
     private boolean enableShutdownHook;
-    private boolean enableChannelMonitorAgent;
     private ObjectMapper payloadMapper = createMessageEnvelopeMapper();
     private MessageGroupStrategy messageGroupStrategy;
 
@@ -84,16 +82,6 @@ public MsbContextBuilder enableShutdownHook(boolean enableShutdownHook) {
         return this;
     }
 
-    /**
-     * Specifies if monitoring agent is enabled.
-     * @param enableChannelMonitorAgent - true if monitoring agent is enabled and false otherwise
-     * @return MsbContextBuilder
-     */
-    public MsbContextBuilder enableChannelMonitorAgent(boolean enableChannelMonitorAgent) {
-        this.enableChannelMonitorAgent = enableChannelMonitorAgent;
-        return this;
-    }
-
     /**
      * Specifies payload object mapper to serialize/deserialize message payload
      * @param payloadMapper if not provided default object mapper will be used
@@ -137,10 +125,6 @@ public MsbContext build() {
                 payloadMapper, collectorManagerFactory,
                 new MutableCallbackHandler());
 
-        if (enableChannelMonitorAgent) {
-            DefaultChannelMonitorAgent.start(msbContext);
-        }
-        
         if (enableShutdownHook) {
             Runtime.getRuntime().addShutdownHook(new Thread("MSB shutdown hook") {
                 @Override
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
index 0952c3b5..c3c37af0 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ObjectFactory.java
@@ -2,11 +2,8 @@
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.JsonNode;
-import io.github.tcdl.msb.api.monitor.AggregatorStats;
-import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator;
 
 import java.lang.reflect.Type;
-import java.util.Set;
 
 /**
  * Provides methods for creation client-facing API objects.
@@ -205,16 +202,4 @@ default  ResponderServer createResponderServer(String namespace, MessageTempl
      * using object mapper from {@link MsbContext}
      */
     PayloadConverter getPayloadConverter();
-
-    /**
-     * @param aggregatorStatsHandler this handler is invoked whenever statistics is updated via announcement channel or heartbeats.
-     *                               THE HANDLER SHOULD BE THREAD SAFE because it may be invoked from parallel threads.
-     * @return new instance of {@link ChannelMonitorAggregator}
-     */
-    ChannelMonitorAggregator createChannelMonitorAggregator(Callback aggregatorStatsHandler);
-
-    /**
-     * Shuts down the factory and all the objects that were created by it.
-     */
-    void shutdown();
 }
\ No newline at end of file
diff --git a/core/src/main/java/io/github/tcdl/msb/api/monitor/AggregatorStats.java b/core/src/main/java/io/github/tcdl/msb/api/monitor/AggregatorStats.java
deleted file mode 100644
index 3a6fa557..00000000
--- a/core/src/main/java/io/github/tcdl/msb/api/monitor/AggregatorStats.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package io.github.tcdl.msb.api.monitor;
-
-import io.github.tcdl.msb.config.ServiceDetails;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Represents aggregated statistics collected from multiple instances of microservices
- * (that is transmitted over the bus by from their {@link io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent}s).
- */
-public class AggregatorStats {
-
-    /**
-     * Collects information about all topics that we know of.
-     * 

- * Structure of the map: topics name -> stats - */ - private Map topicInfoMap = new ConcurrentHashMap<>(); - - /** - * Collects information about all instances of microservices that we know of. - *

- * Structure of the map: service instance id -> details - */ - private Map serviceDetailsById = new ConcurrentHashMap<>(); - - public Map getTopicInfoMap() { - return topicInfoMap; - } - - public Map getServiceDetailsById() { - return serviceDetailsById; - } - - @Override public String toString() { - return String.format("AggregatorStats [topicInfoMap=%s, serviceDetailsById=%s]", topicInfoMap, serviceDetailsById); - } - -} diff --git a/core/src/main/java/io/github/tcdl/msb/api/monitor/AggregatorTopicStats.java b/core/src/main/java/io/github/tcdl/msb/api/monitor/AggregatorTopicStats.java deleted file mode 100644 index e9276674..00000000 --- a/core/src/main/java/io/github/tcdl/msb/api/monitor/AggregatorTopicStats.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.tcdl.msb.api.monitor; - -import javax.annotation.Nullable; -import java.time.Instant; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentSkipListSet; - -/** - * Represents statistics for the given topic - */ -public class AggregatorTopicStats { - /** - * Instance ids of services producing to this topic - */ - private Set producers = new ConcurrentSkipListSet<>(); - - /** - * Instance ids of services consuming from this topics - */ - private Set consumers = new ConcurrentSkipListSet<>(); - - /** - * Time when the last message was produced to the topic - */ - private Instant lastProducedAt; - - /** - * Time when the last message was consumed from the topic - */ - private Instant lastConsumedAt; - - public AggregatorTopicStats() { - } - - public AggregatorTopicStats(@Nullable AggregatorTopicStats aggregatorTopicStats) { - if (aggregatorTopicStats != null) { - this.producers = aggregatorTopicStats.producers; - this.consumers = aggregatorTopicStats.consumers; - this.lastProducedAt = aggregatorTopicStats.lastProducedAt; - this.lastConsumedAt = aggregatorTopicStats.lastConsumedAt; - } - } - - public AggregatorTopicStats withProducers(Set producers) { - this.producers = producers; - return this; - } - - public AggregatorTopicStats withConsumers(Set consumers) { - this.consumers = consumers; - return this; - } - - public AggregatorTopicStats withLastProducedAt(Instant lastProducedAt) { - this.lastProducedAt = lastProducedAt; - return this; - } - - public AggregatorTopicStats withLastConsumedAt(Instant lastConsumedAt) { - this.lastConsumedAt = lastConsumedAt; - return this; - } - - public Set getProducers() { - return producers; - } - - public Set getConsumers() { - return consumers; - } - - public Instant getLastProducedAt() { - return lastProducedAt; - } - - public Instant getLastConsumedAt() { - return lastConsumedAt; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - AggregatorTopicStats that = (AggregatorTopicStats) o; - return Objects.equals(producers, that.producers) && - Objects.equals(consumers, that.consumers) && - Objects.equals(lastProducedAt, that.lastProducedAt) && - Objects.equals(lastConsumedAt, that.lastConsumedAt); - } - - @Override - public int hashCode() { - return Objects.hash(producers, consumers, lastProducedAt, lastConsumedAt); - } - - @Override - public String toString() { - return String.format("AggregatorTopicStats [producers=%s, consumers=%s, lastProducedAt=%s, lastConsumedAt=%s]", producers, consumers, lastProducedAt, - lastConsumedAt); - } -} diff --git a/core/src/main/java/io/github/tcdl/msb/api/monitor/ChannelMonitorAggregator.java b/core/src/main/java/io/github/tcdl/msb/api/monitor/ChannelMonitorAggregator.java deleted file mode 100644 index 3738a8d4..00000000 --- a/core/src/main/java/io/github/tcdl/msb/api/monitor/ChannelMonitorAggregator.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.tcdl.msb.api.monitor; - -import io.github.tcdl.msb.api.Callback; -import io.github.tcdl.msb.api.ObjectFactory; - -/** - * Gathers statistics over the bus from other running microservices that have {@link io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent} activated via - * {@link io.github.tcdl.msb.api.MsbContextBuilder#enableChannelMonitorAgent(boolean)}. The statistics is taken from 2 sources: - * - * 1. By listening to {@link io.github.tcdl.msb.support.Utils#TOPIC_ANNOUNCE} - * 2. By sending periodic heartbeats to {@link io.github.tcdl.msb.support.Utils#TOPIC_HEARTBEAT} and analysing responses. This responses will be aggregated and - * then overwrite stats with most recent information to detect that some microservices went down. - * - * Typical lifecycle for this aggregator is: - * 1. Create instance via {@link ObjectFactory#createChannelMonitorAggregator(Callback)} - * 2. Activate the aggregator via {@link #start()} - * 3. The registered handler processes the stats from the bus - * 4. Deactivate the aggregator via {@link #stop()} - */ -public interface ChannelMonitorAggregator { - - /** - * See {@link #start(boolean, long, int)} - */ - long DEFAULT_HEARTBEAT_INTERVAL_MS = 10000; - - /** - * See {@link #start(boolean, long, int)} - */ - int DEFAULT_HEARTBEAT_TIMEOUT_MS = 5000; - - /** - * Convenience method that activates the aggregator with default heartbeat parameters - */ - default void start() { - start(true, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_HEARTBEAT_TIMEOUT_MS); - } - - /** - * Activates the aggregator with the given parameters - * @param activateHeartbeats if equal to false then no periodic heartbeats are sent and stats is obtained only from announcement channel - * @param heartbeatIntervalMs Interval in milliseconds between heartbeat requests - * @param heartbeatTimeoutMs how long does the aggregator waits in milliseconds for responses after each heartbeat - */ - void start(boolean activateHeartbeats, long heartbeatIntervalMs, int heartbeatTimeoutMs); - - /** - * Deactivates this aggregator. After this method is invoked the object is not usable. This method can be invoked multiple times. - */ - void stop(); -} diff --git a/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java index e5548169..060e37a3 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/MsbContextImpl.java @@ -57,7 +57,6 @@ public synchronized void shutdown() { isShutdownComplete = true; LOG.info("Shutting down MSB context..."); shutdownCallbackHandler.runCallbacks(); - objectFactory.shutdown(); timeoutManager.shutdown(); channelManager.shutdown(); LOG.info("MSB context has been shut down."); diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java index f5bf89c2..dfeed4de 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ObjectFactoryImpl.java @@ -2,30 +2,17 @@ import com.fasterxml.jackson.core.type.TypeReference; import io.github.tcdl.msb.api.*; -import io.github.tcdl.msb.api.monitor.AggregatorStats; -import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; -import io.github.tcdl.msb.config.MsbConfig; -import io.github.tcdl.msb.monitor.aggregator.DefaultChannelMonitorAggregator; import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.lang.reflect.Type; -import java.util.Collections; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; /** * Provides methods for creation {@link Requester} and {@link ResponderServer}. */ public class ObjectFactoryImpl implements ObjectFactory { - private static Logger LOG = LoggerFactory.getLogger(ObjectFactoryImpl.class); private MsbContextImpl msbContext; private PayloadConverter payloadConverter; - private ChannelMonitorAggregator channelMonitorAggregator; public ObjectFactoryImpl(MsbContextImpl msbContext) { super(); @@ -85,37 +72,6 @@ public PayloadConverter getPayloadConverter() { return payloadConverter; } - /** - * {@inheritDoc} - */ - @Override - public synchronized ChannelMonitorAggregator createChannelMonitorAggregator(Callback aggregatorStatsHandler) { - ThreadFactory threadFactory = new BasicThreadFactory.Builder() - .namingPattern("monitor-aggregator-heartbeat-thread-%d") - .daemon(true) - .build(); - ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1, threadFactory); - - channelMonitorAggregator = createDefaultChannelMonitorAggregator(aggregatorStatsHandler, scheduledExecutorService); - return channelMonitorAggregator; - } - - /** - * {@inheritDoc} - */ - @Override - public synchronized void shutdown() { - LOG.info("Shutting down..."); - if (channelMonitorAggregator != null) { - channelMonitorAggregator.stop(); - } - LOG.info("Shutdown complete"); - } - - DefaultChannelMonitorAggregator createDefaultChannelMonitorAggregator(Callback aggregatorStatsHandler, ScheduledExecutorService scheduledExecutorService) { - return new DefaultChannelMonitorAggregator(msbContext, scheduledExecutorService, aggregatorStatsHandler); - } - private static TypeReference toTypeReference(Class clazz) { return new TypeReference() { @Override diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/agent/AgentTopicStats.java b/core/src/main/java/io/github/tcdl/msb/monitor/agent/AgentTopicStats.java deleted file mode 100644 index 29237b59..00000000 --- a/core/src/main/java/io/github/tcdl/msb/monitor/agent/AgentTopicStats.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.tcdl.msb.monitor.agent; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.time.Instant; -import java.util.Objects; - -/** - * Effectively immutable class that contains statistics for a topic. - */ -public class AgentTopicStats { - /** Indicates whether this microservice produces to the topic */ - private boolean producers; - - /** Indicates whether this microservice consumes from the topic */ - private boolean consumers; - - /** Time when this microservice produced to the topic for the last time. */ - private Instant lastProducedAt; - - /** Time when this microservice consumed from the topic for the last time */ - private Instant lastConsumedAt; - - public AgentTopicStats() { - } - - @SuppressWarnings("unused") - public AgentTopicStats(@JsonProperty boolean producers, @JsonProperty boolean consumers, @JsonProperty Instant lastProducedAt, @JsonProperty Instant lastConsumedAt) { - this.producers = producers; - this.consumers = consumers; - this.lastProducedAt = lastProducedAt; - this.lastConsumedAt = lastConsumedAt; - } - - public AgentTopicStats(AgentTopicStats agentTopicStats) { - this.consumers = agentTopicStats.consumers; - this.producers = agentTopicStats.producers; - this.lastConsumedAt = agentTopicStats.lastConsumedAt; - this.lastProducedAt = agentTopicStats.lastProducedAt; - } - - public boolean isProducers() { - return producers; - } - - public boolean isConsumers() { - return consumers; - } - - public Instant getLastProducedAt() { - return lastProducedAt; - } - - public Instant getLastConsumedAt() { - return lastConsumedAt; - } - - public AgentTopicStats withProducers(boolean producers) { - AgentTopicStats newTopic = new AgentTopicStats(this); - newTopic.producers = producers; - return newTopic; - } - - public AgentTopicStats withConsumers(boolean consumers) { - AgentTopicStats newTopic = new AgentTopicStats(this); - newTopic.consumers = consumers; - return newTopic; - } - - public AgentTopicStats withLastProducedAt(Instant lastProducedAt) { - AgentTopicStats newTopic = new AgentTopicStats(this); - newTopic.lastProducedAt = lastProducedAt; - return newTopic; - } - - public AgentTopicStats withLastConsumedAt(Instant lastConsumedAt) { - AgentTopicStats newTopic = new AgentTopicStats(this); - newTopic.lastConsumedAt = lastConsumedAt; - return newTopic; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - AgentTopicStats that = (AgentTopicStats) o; - return Objects.equals(producers, that.producers) && - Objects.equals(consumers, that.consumers) && - Objects.equals(lastProducedAt, that.lastProducedAt) && - Objects.equals(lastConsumedAt, that.lastConsumedAt); - } - - @Override - public int hashCode() { - return Objects.hash(producers, consumers, lastProducedAt, lastConsumedAt); - } - - @Override public String toString() { - return String.format("AgentTopicStats [producers=%s, consumers=%s, lastProducedAt=%s, lastConsumedAt=%s]", producers, consumers, lastProducedAt, - lastConsumedAt); - } -} \ No newline at end of file diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/agent/ChannelMonitorAgent.java b/core/src/main/java/io/github/tcdl/msb/monitor/agent/ChannelMonitorAgent.java deleted file mode 100644 index a52e1ed2..00000000 --- a/core/src/main/java/io/github/tcdl/msb/monitor/agent/ChannelMonitorAgent.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.tcdl.msb.monitor.agent; - -import io.github.tcdl.msb.ChannelManager; - -/** - * Observer interface that allows to subscribe to different events related to - * consuming from and producing to topics. - * - * The implementation is intended to be injected into {@link ChannelManager} instance. - */ -public interface ChannelMonitorAgent { - /** - * Fired when topic producer is created. Typically this happens just before publishing of the first message - * to the given topic. - * - * @param topicName - */ - void producerTopicCreated(String topicName); - - /** - * Fired when consumer is created that listens to the messages on the given topic. - * - * @param topicName - */ - void consumerTopicCreated(String topicName); - - /** - * Fired when consumer is removed for the given topic. The consumer is not longer able to listen to any messages - * on that topic. - * - * @param topicName - */ - void consumerTopicRemoved(String topicName); - - /** - * Fired when a message is sent to the given topic. - * - * @param topicName - */ - void producerMessageSent(String topicName); - - /** - * Fired when a message is consumed from the given topic. - * - * @param topicName - */ - void consumerMessageReceived(String topicName); -} diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java b/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java deleted file mode 100644 index 45f3554f..00000000 --- a/core/src/main/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgent.java +++ /dev/null @@ -1,154 +0,0 @@ -package io.github.tcdl.msb.monitor.agent; - -import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.Producer; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.Responder; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.api.message.payload.RestPayload; -import io.github.tcdl.msb.impl.MsbContextImpl; -import io.github.tcdl.msb.impl.ResponderImpl; -import io.github.tcdl.msb.message.MessageFactory; -import io.github.tcdl.msb.support.Utils; - -import java.time.Clock; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * This implementation maintains statistics over all topics. It broadcasts that statistics over the bus for special monitoring microservices. The overall - * process consists of the following steps: - * - * 1. The agent sends an announcement message each time when a new consumer or producer is created for some topic - * 2. The agent listens on special heartbeat topic for periodic heartbeat messages - * 3. The agent sends the current statistics in response to the heartbeat. - */ -public class DefaultChannelMonitorAgent implements ChannelMonitorAgent { - private MsbContextImpl msbContext; - private ChannelManager channelManager; - private MessageFactory messageFactory; - private Clock clock; - - /** - * This map contains statistics info per topic. - */ - Map topicInfoMap = new ConcurrentHashMap<>(); - - public DefaultChannelMonitorAgent(MsbContextImpl msbContext) { - this.msbContext = msbContext; - - this.channelManager = msbContext.getChannelManager(); - this.messageFactory = msbContext.getMessageFactory(); - this.clock = msbContext.getClock(); - } - - /** - * Convenience factory method that creates the agent instance and starts it. - */ - public static void start(MsbContextImpl msbContext) { - new DefaultChannelMonitorAgent(msbContext).start(); - } - - /** - * Start listening on the heartbeat topic and injects itself in the channel manager instance. - * - * @return this channel manages instance. Might be useful for chaining calls. - */ - public DefaultChannelMonitorAgent start() { - channelManager.subscribe(Utils.TOPIC_HEARTBEAT, // Launch listener for heartbeat topic - (message, acknowledgeHandler) -> { - Responder responder = new ResponderImpl(null, message, msbContext); - RestPayload payload = new RestPayload.Builder>() - .withBody(topicInfoMap) - .build(); - responder.send(payload); - }); - - channelManager.setChannelMonitorAgent(this); // Inject itself in channel manager - - return this; - } - - /** {@inheritDoc} */ - @Override - public void producerTopicCreated(String topicName) { - if (Utils.isServiceTopic(topicName)) { - return; - } - - topicInfoMap.compute(topicName, - (key, agentTopicStats) -> ensureNotNull(agentTopicStats).withProducers(true)); - - doAnnounce(); - } - - /** {@inheritDoc} */ - @Override - public void consumerTopicCreated(String topicName) { - if (Utils.isServiceTopic(topicName)) { - return; - } - - topicInfoMap.compute(topicName, - (key, agentTopicStats) -> ensureNotNull(agentTopicStats).withConsumers(true)); - - doAnnounce(); - } - - /** {@inheritDoc} */ - @Override - public void consumerTopicRemoved(String topicName) { - if (Utils.isServiceTopic(topicName)) { - return; - } - - topicInfoMap.compute(topicName, - (key, agentTopicStats) -> ensureNotNull(agentTopicStats).withConsumers(false)); - } - - /** {@inheritDoc} */ - @Override - public void producerMessageSent(String topicName) { - if (Utils.isServiceTopic(topicName)) { - return; - } - - Instant now = clock.instant(); - topicInfoMap.compute(topicName, - (key, agentTopicStats) -> ensureNotNull(agentTopicStats).withLastProducedAt(now)); - } - - /** {@inheritDoc} */ - @Override - public void consumerMessageReceived(String topicName) { - if (Utils.isServiceTopic(topicName)) { - return; - } - - Instant now = clock.instant(); - topicInfoMap.compute(topicName, - (key, agentTopicStats) -> ensureNotNull(agentTopicStats).withLastConsumedAt(now)); - } - - private AgentTopicStats ensureNotNull(AgentTopicStats topicStats) { - return (topicStats != null) ? topicStats : new AgentTopicStats(); - } - - /** - * Makes broadcast of the current statistics. - */ - private void doAnnounce() { - RestPayload payload = new RestPayload.Builder>() - .withBody(topicInfoMap) - .build(); - - Producer producer = channelManager.findOrCreateProducer(Utils.TOPIC_ANNOUNCE, RequestOptions.DEFAULTS); - - Message.Builder messageBuilder = messageFactory.createBroadcastMessageBuilder(Utils.TOPIC_ANNOUNCE, new MessageTemplate()); - Message announcementMessage = messageFactory.createBroadcastMessage(messageBuilder, payload); - - producer.publish(announcementMessage); - } -} diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/agent/NoopChannelMonitorAgent.java b/core/src/main/java/io/github/tcdl/msb/monitor/agent/NoopChannelMonitorAgent.java deleted file mode 100644 index b212a2be..00000000 --- a/core/src/main/java/io/github/tcdl/msb/monitor/agent/NoopChannelMonitorAgent.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.tcdl.msb.monitor.agent; - -/** - * Empty implementation that does nothing for each event. - */ -public class NoopChannelMonitorAgent implements ChannelMonitorAgent { - /** {@inheritDoc} */ - @Override - public void producerTopicCreated(String topicName) { - } - - /** {@inheritDoc} */ - @Override - public void consumerTopicCreated(String topicName) { - } - - /** {@inheritDoc} */ - @Override - public void consumerTopicRemoved(String topicName) { - } - - /** {@inheritDoc} */ - @Override - public void producerMessageSent(String topicName) { - } - - /** {@inheritDoc} */ - @Override - public void consumerMessageReceived(String topicName) { - } -} diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java b/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java deleted file mode 100644 index c33ead82..00000000 --- a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregator.java +++ /dev/null @@ -1,163 +0,0 @@ -package io.github.tcdl.msb.monitor.aggregator; - -import static io.github.tcdl.msb.support.Utils.TOPIC_ANNOUNCE; -import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.api.AcknowledgementHandler; -import io.github.tcdl.msb.api.Callback; -import io.github.tcdl.msb.api.ObjectFactory; -import io.github.tcdl.msb.api.exception.JsonConversionException; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.api.message.MetaMessage; -import io.github.tcdl.msb.api.message.payload.RestPayload; -import io.github.tcdl.msb.api.monitor.AggregatorStats; -import io.github.tcdl.msb.api.monitor.AggregatorTopicStats; -import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; -import io.github.tcdl.msb.config.ServiceDetails; -import io.github.tcdl.msb.impl.MsbContextImpl; -import io.github.tcdl.msb.monitor.agent.AgentTopicStats; -import io.github.tcdl.msb.support.Utils; - -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class DefaultChannelMonitorAggregator implements ChannelMonitorAggregator { - private static final Logger LOG = LoggerFactory.getLogger(DefaultChannelMonitorAggregator.class); - - private ChannelManager channelManager; - private ObjectFactory objectFactory; - private ObjectMapper messageMapper; - private ScheduledExecutorService scheduledExecutorService; - private Callback handler; - - AggregatorStats masterAggregatorStats = new AggregatorStats(); - - public DefaultChannelMonitorAggregator(MsbContextImpl msbContext, ScheduledExecutorService scheduledExecutorService, - Callback aggregatorStatsHandler) { - this.channelManager = msbContext.getChannelManager(); - this.objectFactory = msbContext.getObjectFactory(); - this.messageMapper = msbContext.getPayloadMapper(); - this.scheduledExecutorService = scheduledExecutorService; - this.handler = aggregatorStatsHandler; - } - - @Override - public void start(boolean activateHeartbeats, long heartbeatIntervalMs, int heartbeatTimeoutMs) { - channelManager.subscribe(TOPIC_ANNOUNCE, this::onAnnounce); - LOG.debug("Subscribed to {}", TOPIC_ANNOUNCE); - - if (activateHeartbeats) { - Runnable heartbeatTask = new HeartbeatTask(heartbeatTimeoutMs, objectFactory, this::onHeartbeatResponses); - scheduledExecutorService.scheduleAtFixedRate(heartbeatTask, 0, heartbeatIntervalMs, TimeUnit.MILLISECONDS); - LOG.debug("Periodic heartbeats activated"); - } - - LOG.info("DefaultChannelMonitorAggregator started"); - } - - @Override - public void stop() { - channelManager.unsubscribe(TOPIC_ANNOUNCE); - scheduledExecutorService.shutdown(); - LOG.info("DefaultChannelMonitorAggregator stopped"); - } - - void onHeartbeatResponses(List heartbeatResponses) { - LOG.debug("Handling heartbeat responses {}...", heartbeatResponses); - AggregatorStats aggregatorStats = new AggregatorStats(); - boolean successfullyAggregatedAtLeastOne = false; - for (Message msg : heartbeatResponses) { - if (aggregateInfo(aggregatorStats, msg)) { - successfullyAggregatedAtLeastOne = true; - } - } - if (successfullyAggregatedAtLeastOne) { - LOG.debug("Calling registered handler for heartbeat statistics {}...", masterAggregatorStats); - handler.call(aggregatorStats); - masterAggregatorStats = aggregatorStats; - LOG.debug("Heartbeat responses processed"); - } - } - - void onAnnounce(Message announcementMessage, AcknowledgementHandler acknowledgeHandler) { - LOG.debug(String.format("Handling announcement message %s...", announcementMessage)); - - boolean successfullyAggregated = aggregateInfo(masterAggregatorStats, announcementMessage); - - if (successfullyAggregated) { - LOG.debug("Calling registered handler for announcement statistics {}...", masterAggregatorStats); - handler.call(masterAggregatorStats); - LOG.debug("Announcement message processed"); - } - } - - boolean aggregateInfo(AggregatorStats aggregatorStats, Message message) { - - JsonNode rawPayload = message.getRawPayload(); - - if (!Utils.isPayloadPresent(rawPayload)) { - LOG.error("Unable to convert message. Message payload is empty."); - return false; - } - - try { - RestPayload> payload = Utils - .convert(rawPayload, new TypeReference>>() { - }, messageMapper); - MetaMessage meta = message.getMeta(); - ServiceDetails serviceDetails = meta.getServiceDetails(); - String instanceId = serviceDetails.getInstanceId(); - aggregatorStats.getServiceDetailsById().put(instanceId, serviceDetails); - - Map agentTopicStatsMap = payload.getBody(); - aggregateTopicStats(aggregatorStats, agentTopicStatsMap, instanceId); - } catch (JsonConversionException e) { - LOG.error("Unable to convert message.", e); - return false; - } - - return true; - } - - void aggregateTopicStats(AggregatorStats aggregatorStats, Map agentTopicStatsMap, String instanceId) { - for (Entry entry : agentTopicStatsMap.entrySet()) { - String topic = entry.getKey(); - AgentTopicStats agentTopicStats = entry.getValue(); - - aggregatorStats.getTopicInfoMap().compute(topic, (topic1, oldValue) -> { - AggregatorTopicStats newValue = new AggregatorTopicStats(oldValue); - - if (agentTopicStats.isConsumers()) { - newValue.getConsumers().add(instanceId); - if (newValue.getLastConsumedAt() == null) { - newValue.withLastConsumedAt(agentTopicStats.getLastConsumedAt()); - } else if (agentTopicStats.getLastConsumedAt() != null - && agentTopicStats.getLastConsumedAt().isAfter(newValue.getLastConsumedAt())) { - newValue.withLastConsumedAt(agentTopicStats.getLastConsumedAt()); - } - } - - if (agentTopicStats.isProducers()) { - newValue.getProducers().add(instanceId); - if (newValue.getLastProducedAt() == null) { - newValue.withLastProducedAt(agentTopicStats.getLastProducedAt()); - } else if (agentTopicStats.getLastProducedAt() != null - && agentTopicStats.getLastProducedAt().isAfter(newValue.getLastProducedAt())) { - newValue.withLastProducedAt(agentTopicStats.getLastProducedAt()); - } - } - - return newValue; - }); - } - } -} diff --git a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTask.java b/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTask.java deleted file mode 100644 index 448b82d7..00000000 --- a/core/src/main/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTask.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.tcdl.msb.monitor.aggregator; - -import io.github.tcdl.msb.api.Callback; -import io.github.tcdl.msb.api.ObjectFactory; -import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.api.message.payload.RestPayload; -import io.github.tcdl.msb.support.Utils; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Sends heartbeat requests and passes the aggregated responses to the registered handler. This task is meant to be scheduled periodically. - */ -public class HeartbeatTask implements Runnable { - - private static final Logger LOG = LoggerFactory.getLogger(HeartbeatTask.class); - - private int heartbeatTimeoutMs; - private ObjectFactory objectFactory; - private Callback> heartbeatHandler; - - public HeartbeatTask(int heartbeatTimeoutMs, ObjectFactory objectFactory, Callback> heartbeatHandler) { - this.heartbeatTimeoutMs = heartbeatTimeoutMs; - this.objectFactory = objectFactory; - this.heartbeatHandler = heartbeatHandler; - } - - @Override - public void run() { - try { - LOG.debug("Sending heartbeat request..."); - RequestOptions requestOptions = new RequestOptions.Builder() - .withResponseTimeout(heartbeatTimeoutMs) - .withWaitForResponses(-1) - .build(); - - RestPayload emptyPayload = new RestPayload.Builder().build(); - - List messages = Collections.synchronizedList(new LinkedList<>()); - objectFactory.createRequester(Utils.TOPIC_HEARTBEAT, requestOptions) - .onRawResponse((message, achHandler) -> {messages.add(message);}) - .onEnd(end -> heartbeatHandler.call(messages)) - .publish(emptyPayload); - - LOG.debug("Heartbeat request sent"); - } catch (Exception e) { - LOG.error("Error during heartbeat invocation", e); - } - } -} diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java index dfb4e066..b726eb1a 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java @@ -1,41 +1,30 @@ package io.github.tcdl.msb; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - +import com.fasterxml.jackson.databind.ObjectMapper; +import com.googlecode.junittoolbox.MultithreadingTester; import io.github.tcdl.msb.adapters.AdapterFactory; import io.github.tcdl.msb.adapters.AdapterFactoryLoader; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.RequesterResponderIT; -import io.github.tcdl.msb.api.message.Message; +import io.github.tcdl.msb.api.ResponderOptions; import io.github.tcdl.msb.collector.CollectorManager; import io.github.tcdl.msb.config.MsbConfig; -import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.TestUtils; - -import java.time.Clock; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - import io.github.tcdl.msb.threading.ConsumerExecutorFactoryImpl; import io.github.tcdl.msb.threading.MessageHandlerInvoker; import io.github.tcdl.msb.threading.ThreadPoolMessageHandlerInvoker; import org.junit.Before; import org.junit.Test; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.googlecode.junittoolbox.MultithreadingTester; +import java.time.Clock; + +import static org.mockito.Mockito.*; public class ChannelManagerConcurrentTest { private ChannelManager channelManager; - private ChannelMonitorAgent mockChannelMonitorAgent; - private MessageHandler messageHandlerMock; + private AdapterFactory adapterFactory; @Before public void setUp() { @@ -44,22 +33,17 @@ public void setUp() { JsonValidator validator = new JsonValidator(); ObjectMapper messageMapper = TestUtils.createMessageMapper(); MessageHandlerInvoker messageHandlerInvoker = new ThreadPoolMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(), new ConsumerExecutorFactoryImpl()); - AdapterFactory adapterFactory = new AdapterFactoryLoader(msbConfig).getAdapterFactory(); + adapterFactory = spy(new AdapterFactoryLoader(msbConfig).getAdapterFactory()); this.channelManager = new ChannelManager(msbConfig, clock, validator, messageMapper, adapterFactory, messageHandlerInvoker); - - mockChannelMonitorAgent = mock(ChannelMonitorAgent.class); - channelManager.setChannelMonitorAgent(mockChannelMonitorAgent); - messageHandlerMock = mock(MessageHandler.class); } @Test public void testProducerCachedMultithreadInteraction() { String topic = "topic:test-producer-cached-multithreaded"; - new MultithreadingTester().add(() -> { - channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); - verify(mockChannelMonitorAgent).producerTopicCreated(topic); - }).run(); + new MultithreadingTester().add(() -> {channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS);}).run(); + + verify(adapterFactory, times(1)).createProducerAdapter(eq(topic), eq(RequestOptions.DEFAULTS)); } @Test @@ -67,57 +51,14 @@ public void testConsumerUnsubscribeMultithreadInteraction() { String topic = "topic:test-remove-consumer-multithreaded"; CollectorManager collectorManager = new CollectorManager(topic, channelManager); - channelManager.subscribeForResponses(topic, collectorManager); - new MultithreadingTester().add(() -> { - channelManager.unsubscribe(topic); - verify(mockChannelMonitorAgent, timeout(400)).consumerTopicRemoved(topic); - }).run(); - } + ConsumerAdapter consumerAdapter = mock(ConsumerAdapter.class); + when(adapterFactory.createConsumerAdapter(eq(topic), any(ResponderOptions.class), eq(true))).thenReturn(consumerAdapter); - @Test - public void testPublishMessageInvokesAgentMultithreadInteraction() throws InterruptedException { - String topic = "topic:test-agent-publish-multithreaded"; - int numberOfThreads = 10; - int numberOfInvocationsPerThread = 20; - - Producer producer = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); - Message message = TestUtils.createSimpleRequestMessage(topic); - - CountDownLatch messagesSent = new CountDownLatch(numberOfThreads * numberOfInvocationsPerThread); - - new MultithreadingTester().numThreads(numberOfThreads).numRoundsPerThread(numberOfInvocationsPerThread).add(() -> { - producer.publish(message); - messagesSent.countDown(); - }).run(); - - assertTrue(messagesSent.await(4000, TimeUnit.MILLISECONDS)); - verify(mockChannelMonitorAgent, atLeast(numberOfThreads * numberOfInvocationsPerThread)).producerMessageSent(topic); - } - - @Test - public void testReceiveMessageInvokesAgentAndEmitsEventMultithreadInteraction() throws InterruptedException { - String topic = "topic:test-agent-consumer-multithreaded"; - int numberOfThreads = 4; - int numberOfInvocationsPerThread = 20; - - Producer producer = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); - Message message = TestUtils.createSimpleRequestMessage(topic); - - CountDownLatch messagesReceived = new CountDownLatch(numberOfThreads * numberOfInvocationsPerThread); - - channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); - channelManager.subscribe(topic, - (msg, acknowledgeHandler) -> { - messagesReceived.countDown(); - }); + channelManager.subscribeForResponses(topic, collectorManager); - new MultithreadingTester().numThreads(numberOfThreads).numRoundsPerThread(numberOfInvocationsPerThread).add(() -> { - producer.publish(message); - }).run(); + new MultithreadingTester().add(() -> {channelManager.unsubscribe(topic);}).run(); - assertTrue(messagesReceived.await(RequesterResponderIT.MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); - verify(mockChannelMonitorAgent, times(numberOfThreads * numberOfInvocationsPerThread)).consumerMessageReceived(topic); + verify(consumerAdapter, times(1)).unsubscribe(); } - } diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java index ed36e927..7b1f8cb6 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java @@ -8,7 +8,6 @@ import io.github.tcdl.msb.api.exception.ConsumerSubscriptionException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.config.MsbConfig; -import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.threading.ConsumerExecutorFactoryImpl; @@ -26,12 +25,10 @@ import java.util.concurrent.TimeUnit; import static org.junit.Assert.*; -import static org.mockito.Mockito.*; public class ChannelManagerTest { private ChannelManager channelManager; - private ChannelMonitorAgent mockChannelMonitorAgent; @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -47,65 +44,47 @@ public void setUp() { AdapterFactory adapterFactory = new AdapterFactoryLoader(msbConfig).getAdapterFactory(); this.channelManager = new ChannelManager(msbConfig, clock, validator, messageMapper, adapterFactory, messageHandlerInvoker); - - mockChannelMonitorAgent = mock(ChannelMonitorAgent.class); - channelManager.setChannelMonitorAgent(mockChannelMonitorAgent); } @Test public void testProducerCached() { String topic = "topic:test-producer-cached"; - // Producer was created and monitor agent notified + // Producer was created Producer producer1 = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); assertNotNull(producer1); - verify(mockChannelMonitorAgent).producerTopicCreated(topic); - // Cached producer was returned and monitor agent wasn't notified + // Cached producer was returned Producer producer2 = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); assertNotNull(producer2); assertSame(producer1, producer2); - verifyNoMoreInteractions(mockChannelMonitorAgent); } @Test public void testMultipleConsumersCantSubscribeOnTheSameTopic() { String topic = "topic:test-consumer"; - // Consumer was created and monitor agent notified + // Consumer was created channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); expectedException.expect(ConsumerSubscriptionException.class); channelManager.subscribe(topic, (message, acknowledgeHandler) -> {}); } @Test - public void testCantSubscribeOnTheSameTopicWithDifferentRoutingKeys() throws Exception { + public void testCantSubscribeOnTheSameTopic() throws Exception { String topic = "interesting:topic"; - String bindingKey1 = "routing.key.one"; - String bindingKey2 = "routing.key.two"; + String bindingKey = "routing.key.one"; - ResponderOptions responderOptions1 = new ResponderOptions.Builder().withBindingKeys(Collections.singleton(bindingKey1)).build(); - ResponderOptions responderOptions2 = new ResponderOptions.Builder().withBindingKeys(Collections.singleton(bindingKey2)).build(); + ResponderOptions responderOptions1 = new ResponderOptions.Builder().withBindingKeys(Collections.singleton(bindingKey)).build(); + ResponderOptions responderOptions2 = new ResponderOptions.Builder().withBindingKeys(Collections.singleton(bindingKey)).build(); channelManager.subscribe(topic, responderOptions1, (message, acknowledgeHandler) -> {}); - verify(mockChannelMonitorAgent).consumerTopicCreated(topic); expectedException.expect(ConsumerSubscriptionException.class); channelManager.subscribe(topic, responderOptions2, (message, acknowledgeHandler) -> {}); } @Test - public void testPublishMessageInvokesAgent() { - String topic = "topic:test-agent-publish"; - - Producer producer = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS); - Message message = TestUtils.createSimpleRequestMessage(topic); - producer.publish(message); - - verify(mockChannelMonitorAgent).producerMessageSent(topic); - } - - @Test - public void testReceiveMessageInvokesAgentAndEmitsEvent() throws InterruptedException { + public void testReceiveMessageInvokesHandler() throws InterruptedException { String topic = "topic:test-agent-consume"; CountDownLatch awaitReceiveEvents = new CountDownLatch(1); @@ -121,47 +100,7 @@ public void testReceiveMessageInvokesAgentAndEmitsEvent() throws InterruptedExce channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS).publish(message); assertTrue(awaitReceiveEvents.await(4000, TimeUnit.MILLISECONDS)); - verify(mockChannelMonitorAgent).consumerMessageReceived(topic); assertNotNull(messageEvent.value); } - @Test - public void testSubscribeUnsubscribeFromBroadcast() { - String topic = "topic:test-unsubscribe-once"; - - channelManager.subscribe(topic, (message, acknowledgeHandler) -> { - }); - channelManager.unsubscribe(topic); - - verify(mockChannelMonitorAgent).consumerTopicRemoved(topic); - } - - @Test - public void testSubscribeUnsubscribeFromMulticast() { - String topic = "topic:test-unsubscribe-once"; - - ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.singleton("routing.key")).build(); - - channelManager.subscribe(topic, responderOptions, (message, acknowledgeHandler) -> { - }); - channelManager.unsubscribe(topic); - - verify(mockChannelMonitorAgent).consumerTopicRemoved(topic); - } - - @Test - public void testSubscribeUnsubscribeSeparateTopics() { - String topic1 = "topic:test-unsubscribe-try-first"; - String topic2 = "topic:test-unsubscribe-try-other"; - - channelManager.subscribe(topic1, (message, acknowledgeHandler) -> { - }); - channelManager.subscribe(topic2, (message, acknowledgeHandler) -> { - }); - - channelManager.unsubscribe(topic1); - verify(mockChannelMonitorAgent).consumerTopicRemoved(topic1); - verify(mockChannelMonitorAgent, never()).consumerTopicRemoved(topic2); - } - } \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index b0728877..3c0ed2d4 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -1,41 +1,38 @@ package io.github.tcdl.msb; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; - -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.typesafe.config.ConfigFactory; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; -import io.github.tcdl.msb.threading.MessageHandlerInvoker; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.exception.JsonConversionException; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.MetaMessage; import io.github.tcdl.msb.api.message.Topics; import io.github.tcdl.msb.collector.ConsumedMessagesAwareMessageHandler; import io.github.tcdl.msb.config.MsbConfig; -import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.TestUtils; import io.github.tcdl.msb.support.Utils; - -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; -import java.util.Map; -import java.util.Optional; - +import io.github.tcdl.msb.threading.MessageHandlerInvoker; import org.apache.commons.lang3.StringUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.typesafe.config.ConfigFactory; import org.slf4j.MDC; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.util.Map; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + @RunWith(MockitoJUnitRunner.class) public class ConsumerTest { @@ -57,9 +54,6 @@ public class ConsumerTest { @Mock private MsbConfig msbConfMock; - @Mock - private ChannelMonitorAgent channelMonitorAgentMock; - @Mock private MessageHandler messageHandlerMock; @@ -100,48 +94,43 @@ public void setUp() { @Test(expected = NullPointerException.class) public void testCreateConsumerNullAdapter() { - new Consumer(null, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + new Consumer(null, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullTopic() { - new Consumer(adapterMock, messageHandlerInvokerMock, null, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, null, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullMessageHandler() { - new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, null, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, null, msbConfMock, clock, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullMsbConf() { - new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, null, clock, channelMonitorAgentMock, validator, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, null, clock, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullClock() { - new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, null, channelMonitorAgentMock, validator, messageMapper); - } - - @Test(expected = NullPointerException.class) - public void testCreateConsumerNullMonitorAgent() { - new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, null, validator, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, null, validator, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullValidator() { - new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, null, messageMapper); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, null, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateConsumerNullMessageMapper() { - new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, null); + new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, null); } @Test public void testValidMessageProcessedBySubscriber() throws JsonConversionException { Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); @@ -151,7 +140,7 @@ public void testValidMessageProcessedBySubscriber() throws JsonConversionExcepti @Test public void testConsumedMessagesAwareMessageHandlerNotifiedWhenMessageHandled() throws JsonConversionException { Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, consumedMessagesAwareMessageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, consumedMessagesAwareMessageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); @@ -163,7 +152,7 @@ public void testConsumedMessagesAwareMessageHandlerNotifiedWhenMessageLost() thr doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokerMock).execute(any(), any(), any()); Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, consumedMessagesAwareMessageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, consumedMessagesAwareMessageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); @@ -177,7 +166,7 @@ public void testMessageHandlerCantBeResolved() throws JsonConversionException { .thenReturn(Optional.empty()); Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); @@ -190,7 +179,7 @@ public void testMessageHandlerInvokeException() throws JsonConversionException { doThrow(new RuntimeException("Something really unexpected.")).when(messageHandlerInvokerMock).execute(any(), any(), any()); Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); @@ -199,7 +188,7 @@ public void testMessageHandlerInvokeException() throws JsonConversionException { @Test public void testExceptionWhileMessageConvertingProcessedBySubscriber() throws JsonConversionException { - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock); @@ -213,7 +202,7 @@ public void testHandleRawMessageConsumeFromTopicSkipValidation() { // disable validation when(msbConf.isValidateMessage()).thenReturn(false); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); // create a message with required empty namespace Message message = TestUtils.createSimpleRequestMessage(""); @@ -228,7 +217,7 @@ public void testHandleRawMessageConsumeFromTopicSkipValidation() { @Test public void testHandleRawMessageConsumeFromTopic() throws JsonConversionException { Message originalMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); verifyMessageHandled(); @@ -237,7 +226,7 @@ public void testHandleRawMessageConsumeFromTopic() throws JsonConversionExceptio @Test public void testHandleRawMessageConsumeFromTopicValidateThrowException() { MsbConfig msbConf = TestUtils.createMsbConfigurations(); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConf, clock, validator, messageMapper); consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock); verifyMessageNotHandled(); @@ -247,7 +236,7 @@ public void testHandleRawMessageConsumeFromTopicValidateThrowException() { public void testHandleRawMessageConsumeFromServiceTopicValidateThrowException() { String service_topic = "_service:topic"; MsbConfig msbConf = TestUtils.createMsbConfigurations(); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, service_topic, messageHandlerResolverMock, msbConf, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, service_topic, messageHandlerResolverMock, msbConf, clock, validator, messageMapper); consumer.handleRawMessage("{\"body\":\"fake message\"}", acknowledgementHandlerMock); verifyMessageNotHandled(); @@ -256,7 +245,7 @@ public void testHandleRawMessageConsumeFromServiceTopicValidateThrowException() @Test public void testHandleRawMessageConsumeFromTopicExpiredMessage() throws JsonConversionException { Message expiredMessage = createExpiredMsbRequestMessageWithTopicTo(TOPIC); - Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(expiredMessage, messageMapper), acknowledgementHandlerMock); verifyMessageNotHandled(); @@ -315,7 +304,7 @@ public void shutdown() { } }; - Consumer consumer = new Consumer(adapterMock, testInvokeStrategy, TOPIC, messageHandlerResolverMock, msbConfMock, clock, channelMonitorAgentMock, validator, messageMapper); + Consumer consumer = new Consumer(adapterMock, testInvokeStrategy, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); consumer.handleRawMessage(Utils.toJson(originalMessage, messageMapper), acknowledgementHandlerMock); Map map = MDC.getCopyOfContextMap(); diff --git a/core/src/test/java/io/github/tcdl/msb/ProducerTest.java b/core/src/test/java/io/github/tcdl/msb/ProducerTest.java index da26896d..8c5ec682 100644 --- a/core/src/test/java/io/github/tcdl/msb/ProducerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ProducerTest.java @@ -50,39 +50,23 @@ public class ProducerTest { @Test(expected = NullPointerException.class) public void testCreateConsumerProducerNullAdapter() { - new Producer(null, "testTopic", handlerMock, messageMapper); + new Producer(null, "testTopic", messageMapper); } @Test(expected = NullPointerException.class) public void testCreateProducerNullTopic() { - new Producer(adapterMock, null, handlerMock, messageMapper); - } - - @Test(expected = NullPointerException.class) - public void testCreateProducerNullHandler() { - new Producer(adapterMock, "testTopic", null, messageMapper); + new Producer(adapterMock, null, messageMapper); } @Test(expected = NullPointerException.class) public void testCreateProducerNullMapper() { - new Producer(adapterMock, "testTopic", handlerMock, null); - } - - @Test - @SuppressWarnings("unchecked") - public void testPublishVerifyHandlerCalled() { - Message originaMessage = TestUtils.createSimpleRequestMessage(TOPIC); - - Producer producer = new Producer(adapterMock, TOPIC, handlerMock, messageMapper); - producer.publish(originaMessage); - - verify(handlerMock).call(any(Message.class)); + new Producer(adapterMock, "testTopic", null); } @Test public void testPublishWithDefaultRoutingKey() throws Exception { Message originaMessage = TestUtils.createSimpleRequestMessage(TOPIC); - Producer producer = new Producer(adapterMock, TOPIC, handlerMock, messageMapper); + Producer producer = new Producer(adapterMock, TOPIC, messageMapper); producer.publish(originaMessage); verify(adapterMock).publish(anyString(), eq(StringUtils.EMPTY)); } @@ -94,7 +78,7 @@ public void testPublishRawAdapterThrowChannelException() throws ChannelException Mockito.doThrow(ChannelException.class).when(adapterMock).publish(anyString(), anyString()); - Producer producer = new Producer(adapterMock, TOPIC, handlerMock, messageMapper); + Producer producer = new Producer(adapterMock, TOPIC, messageMapper); producer.publish(originaMessage); verify(handlerMock, never()).call(any(Message.class)); @@ -106,7 +90,7 @@ public void testPublishRawAdapterThrowChannelException() throws ChannelException public void testPublishThrowExceptionVerifyCallbackNotSetNotCalled() throws ChannelException { Message brokenMessage = createBrokenRequestMessageWithAndTopicTo(TOPIC); - Producer producer = new Producer(adapterMock, TOPIC, handlerMock, messageMapper); + Producer producer = new Producer(adapterMock, TOPIC, messageMapper); producer.publish(brokenMessage); verify(adapterMock, never()).publish(anyString()); diff --git a/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java b/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java deleted file mode 100644 index c19bcbfc..00000000 --- a/core/src/test/java/io/github/tcdl/msb/api/ChannelMonitorIT.java +++ /dev/null @@ -1,189 +0,0 @@ -package io.github.tcdl.msb.api; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.api.message.payload.RestPayload; -import io.github.tcdl.msb.api.monitor.AggregatorStats; -import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; -import io.github.tcdl.msb.impl.MsbContextImpl; -import io.github.tcdl.msb.monitor.agent.AgentTopicStats; -import io.github.tcdl.msb.monitor.agent.ChannelMonitorAgent; -import io.github.tcdl.msb.monitor.agent.DefaultChannelMonitorAgent; -import io.github.tcdl.msb.support.TestUtils; -import io.github.tcdl.msb.support.Utils; - -import java.time.Instant; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import io.github.tcdl.msb.mock.adapterfactory.TestMsbStorageForAdapterFactory; -import org.apache.commons.lang3.StringUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class ChannelMonitorIT { - - private static final Instant LAST_PRODUCED_TIME = Instant.parse("2007-12-03T15:15:30.00Z"); - private static final Instant LAST_CONSUMED_TIME = Instant.parse("2007-12-03T17:15:30.00Z"); - - private static final int HEARTBEAT_TIMEOUT_MS = 2000; - - MsbContextImpl msbContext; - ChannelMonitorAggregator channelMonitorAggregator; - - private TestMsbStorageForAdapterFactory storage; - - @Before - public void setUp() { - msbContext = TestUtils.createSimpleMsbContext(); - storage = TestMsbStorageForAdapterFactory.extract(msbContext); - } - - @After - public void tearDown() { - channelMonitorAggregator.stop(); - } - - @Test - public void testAnnouncement() throws InterruptedException { - String TOPIC_NAME = "topic1"; - CountDownLatch announcementReceived = monitorPrepareAwaitOnAnnouncement(TOPIC_NAME); - - ChannelMonitorAgent channelMonitorAgent = new DefaultChannelMonitorAgent(msbContext); - channelMonitorAgent.producerTopicCreated(TOPIC_NAME); - - assertTrue("Announcement was not received", announcementReceived.await(RequesterResponderIT.MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); - } - - @Test - public void testAnnouncementUnexpectedMessage() throws InterruptedException { - String TOPIC_NAME = "topic2"; - CountDownLatch announcementReceived = monitorPrepareAwaitOnAnnouncement(TOPIC_NAME); - - //simulate broken announcement in broker - storage.publishIncomingMessage(Utils.TOPIC_ANNOUNCE, StringUtils.EMPTY, - Utils.toJson(TestUtils.createSimpleRequestMessage(Utils.TOPIC_ANNOUNCE), msbContext.getPayloadMapper())); - - assertFalse("Broken announcement was handled", announcementReceived.await(RequesterResponderIT.MESSAGE_TRANSMISSION_TIME / 2, TimeUnit.MILLISECONDS)); - - //verify next correct announcement was handled - ChannelMonitorAgent channelMonitorAgent = new DefaultChannelMonitorAgent(msbContext); - channelMonitorAgent.producerTopicCreated(TOPIC_NAME); - - assertTrue("Announcement was not received", announcementReceived.await(RequesterResponderIT.MESSAGE_TRANSMISSION_TIME, TimeUnit.MILLISECONDS)); - } - - private CountDownLatch monitorPrepareAwaitOnAnnouncement(String topicName) throws InterruptedException { - CountDownLatch announcementReceived = new CountDownLatch(1); - Callback handler = stats -> { - assertTrue(stats.getTopicInfoMap().containsKey(topicName)); - assertEquals(1, stats.getTopicInfoMap().get(topicName).getProducers().size()); - announcementReceived.countDown(); - }; - - channelMonitorAggregator = msbContext.getObjectFactory().createChannelMonitorAggregator(handler); - channelMonitorAggregator.start(false, ChannelMonitorAggregator.DEFAULT_HEARTBEAT_INTERVAL_MS, HEARTBEAT_TIMEOUT_MS); - - return announcementReceived; - } - - @Test - public void testHeartbeatMessage() throws InterruptedException { - String TOPIC_NAME = "topic3"; - - Map topicInfoMap = new HashMap<>(); - topicInfoMap.put(TOPIC_NAME, new AgentTopicStats(true, false, LAST_PRODUCED_TIME, LAST_CONSUMED_TIME)); - - RestPayload payload = new RestPayload.Builder>() - .withBody(topicInfoMap) - .build(); - - CountDownLatch heartBeatResponseReceived = new CountDownLatch(1); - Callback handler = stats -> { - assertTrue(stats.getTopicInfoMap().containsKey(TOPIC_NAME)); - assertEquals(1, stats.getTopicInfoMap().get(TOPIC_NAME).getProducers().size()); - heartBeatResponseReceived.countDown(); - }; - - channelMonitorAggregator = msbContext.getObjectFactory().createChannelMonitorAggregator(handler); - channelMonitorAggregator.start(true, ChannelMonitorAggregator.DEFAULT_HEARTBEAT_INTERVAL_MS, HEARTBEAT_TIMEOUT_MS); - - //need to await for original request for heartbeat to be send to simulate response with same correlationId - Message requestMessage = awaitHeartBeatRequestSent(); - - Message responseMessage = TestUtils.createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(), - requestMessage.getCorrelationId(), - payload); - storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), StringUtils.EMPTY, Utils.toJson(responseMessage, msbContext.getPayloadMapper())); - - assertTrue("Heartbeat response was not received", - heartBeatResponseReceived.await(HEARTBEAT_TIMEOUT_MS * 2, TimeUnit.MILLISECONDS)); - } - - @Test - public void testHeartbeatUnexpectedMessage() throws InterruptedException { - String TOPIC_NAME = "topic4"; - - Map topicInfoMap = new HashMap<>(); - topicInfoMap.put(TOPIC_NAME, new AgentTopicStats(true, false, LAST_PRODUCED_TIME, LAST_CONSUMED_TIME)); - - RestPayload payload = new RestPayload.Builder>() - .withBody(topicInfoMap) - .build(); - - CountDownLatch heartBeatResponseReceived = new CountDownLatch(1); - Callback handler = stats -> { - assertEquals(1, stats.getTopicInfoMap().size()); - heartBeatResponseReceived.countDown(); - }; - - channelMonitorAggregator = msbContext.getObjectFactory().createChannelMonitorAggregator(handler); - channelMonitorAggregator.start(true, ChannelMonitorAggregator.DEFAULT_HEARTBEAT_INTERVAL_MS, HEARTBEAT_TIMEOUT_MS); - - //need to await for original request for heartbeat to be send to simulate response with same correlationId - Message requestMessage = awaitHeartBeatRequestSent(); - - Message brokenResponseMessage1 = TestUtils.createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(), - requestMessage.getCorrelationId(), - " unexpected statistics format received"); - Message brokenResponseMessage2 = TestUtils.createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(), - requestMessage.getCorrelationId(), - " unexpected statistics format received"); - Message responseMessage = TestUtils - .createMsbRequestMessageWithCorrelationId(requestMessage.getTopics().getResponse(), requestMessage.getCorrelationId(), - payload); - //simulate 3 heartbeatResponses: 1 valid and 2 broken - storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), StringUtils.EMPTY, Utils.toJson(brokenResponseMessage1, msbContext.getPayloadMapper())); - storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), StringUtils.EMPTY, Utils.toJson(responseMessage, msbContext.getPayloadMapper())); - storage.publishIncomingMessage(requestMessage.getTopics().getResponse(), StringUtils.EMPTY, Utils.toJson(brokenResponseMessage2, msbContext.getPayloadMapper())); - - assertTrue("Heartbeat response was not received", - heartBeatResponseReceived.await(HEARTBEAT_TIMEOUT_MS * 2, TimeUnit.MILLISECONDS)); - } - - private Message awaitHeartBeatRequestSent() throws InterruptedException { - //need to await for original request for heartbeat to be send to simulate response with same correlationId - CountDownLatch awaitRequestMessage = new CountDownLatch(1); - List outgoingRequestMessages = new LinkedList<>(); - msbContext.getChannelManager().subscribe(Utils.TOPIC_HEARTBEAT, (message, acknowledgeHandler) -> { - outgoingRequestMessages.add(message); - awaitRequestMessage.countDown(); - }); - - //fail the test if not able to get original heartbeat request - assertTrue("Heartbeat original request not captured", - awaitRequestMessage.await(HEARTBEAT_TIMEOUT_MS, TimeUnit.MILLISECONDS)); - - //unsubscribe or else will consume messages from previous run - msbContext.getChannelManager().unsubscribe(Utils.TOPIC_HEARTBEAT); - return outgoingRequestMessages.get(0); - } - -} diff --git a/core/src/test/java/io/github/tcdl/msb/impl/MsbContextImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/MsbContextImplTest.java index 62955b91..af1780e1 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/MsbContextImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/MsbContextImplTest.java @@ -60,7 +60,6 @@ public void testShutdown() { private void verifyShutdownOnce() { verify(shutdownCallbackHandlerMock, times(1)).runCallbacks(); - verify(objectFactoryMock, times(1)).shutdown(); verify(timeoutManagerMock, times(1)).shutdown(); verify(channelManagerMock, times(1)).shutdown(); } diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java index 7589faf6..4e3d2b7f 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ObjectFactoryImplTest.java @@ -1,29 +1,14 @@ package io.github.tcdl.msb.impl; -import io.github.tcdl.msb.api.Callback; -import io.github.tcdl.msb.api.MessageTemplate; -import io.github.tcdl.msb.api.ObjectFactory; -import io.github.tcdl.msb.api.PayloadConverter; -import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.Requester; -import io.github.tcdl.msb.api.ResponderServer; -import io.github.tcdl.msb.api.monitor.AggregatorStats; -import io.github.tcdl.msb.monitor.aggregator.DefaultChannelMonitorAggregator; +import io.github.tcdl.msb.api.*; import io.github.tcdl.msb.support.TestUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; -import java.util.concurrent.ScheduledExecutorService; - import static org.junit.Assert.assertNotNull; -import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ObjectFactoryImplTest { @@ -49,28 +34,6 @@ public void testCreateResponderServer() { assertNotNull(expectedResponderServer); } - @Test - public void testShutdownNoAggregator() { - ObjectFactoryImpl objectFactory = new ObjectFactoryImpl(TestUtils.createMsbContextBuilder().build()); - objectFactory.shutdown(); - } - - @Test - public void testShutdownWithAggregator() { - ObjectFactoryImpl objectFactorySpy = spy(new ObjectFactoryImpl(TestUtils.createMsbContextBuilder().build())); - - @SuppressWarnings("unchecked") - Callback mockAggregatorCallback = mock(Callback.class); - DefaultChannelMonitorAggregator mockChannelMonitorAggregator = mock(DefaultChannelMonitorAggregator.class); - when(objectFactorySpy.createDefaultChannelMonitorAggregator(Mockito.eq(mockAggregatorCallback), any(ScheduledExecutorService.class))).thenReturn( - mockChannelMonitorAggregator); - - objectFactorySpy.createChannelMonitorAggregator(mockAggregatorCallback); - objectFactorySpy.shutdown(); - - verify(mockChannelMonitorAggregator).stop(); - } - @Test public void getPayloadConverter() { ObjectFactory objectFactory = new ObjectFactoryImpl(TestUtils.createMsbContextBuilder().build()); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/PayloadConverterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/PayloadConverterImplTest.java index ec262152..687cb35f 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/PayloadConverterImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/PayloadConverterImplTest.java @@ -21,7 +21,6 @@ public class PayloadConverterImplTest { public void setUp() { MsbContext msbContext = new MsbContextBuilder() .withPayloadMapper(new ObjectMapper()) - .enableChannelMonitorAgent(true) .enableShutdownHook(true) .build(); payloadConverter = msbContext.getObjectFactory().getPayloadConverter(); diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java index ccfcf3f8..1a72f1b1 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java @@ -43,8 +43,9 @@ public ConsumerAdapter createConsumerAdapter(String namespace, boolean isRespons @Override public ConsumerAdapter createConsumerAdapter(String topic, ResponderOptions responderOptions, boolean isResponseTopic) { + ResponderOptions effectiveResponderOptions = responderOptions != null ? responderOptions: ResponderOptions.DEFAULTS; TestMsbConsumerAdapter consumerAdapter = new TestMsbConsumerAdapter(topic, storage); - storage.addConsumerAdapter(topic, responderOptions.getBindingKeys(), consumerAdapter); + storage.addConsumerAdapter(topic, effectiveResponderOptions.getBindingKeys(), consumerAdapter); return consumerAdapter; } diff --git a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java index 65066222..c93f69f0 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/objectfactory/TestMsbObjectFactory.java @@ -3,13 +3,9 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import io.github.tcdl.msb.api.*; -import io.github.tcdl.msb.api.monitor.AggregatorStats; -import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; -import io.github.tcdl.msb.impl.RequesterImpl; import org.apache.commons.lang3.Validate; import java.lang.reflect.Type; -import java.util.Set; /** * {@link ObjectFactory} implementation that captures all requesters/responders params and callbacks to be @@ -76,16 +72,6 @@ public PayloadConverter getPayloadConverter() { return null; } - @Override - public ChannelMonitorAggregator createChannelMonitorAggregator(Callback aggregatorStatsHandler) { - return null; - } - - @Override - public void shutdown() { - - } - private static TypeReference toTypeReference(Class clazz) { return new TypeReference() { @Override diff --git a/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java b/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java deleted file mode 100644 index ffde68e4..00000000 --- a/core/src/test/java/io/github/tcdl/msb/monitor/agent/DefaultChannelMonitorAgentTest.java +++ /dev/null @@ -1,160 +0,0 @@ -package io.github.tcdl.msb.monitor.agent; - -import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.MessageHandler; -import io.github.tcdl.msb.Producer; -import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.collector.TimeoutManager; -import io.github.tcdl.msb.config.ServiceDetails; -import io.github.tcdl.msb.impl.MsbContextImpl; -import io.github.tcdl.msb.message.MessageFactory; -import io.github.tcdl.msb.support.TestUtils; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; - -import static io.github.tcdl.msb.support.Utils.TOPIC_ANNOUNCE; -import static io.github.tcdl.msb.support.Utils.TOPIC_HEARTBEAT; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class DefaultChannelMonitorAgentTest { - private static final Instant CLOCK_INSTANT = Instant.parse("2007-12-03T10:15:30.00Z"); - - private DefaultChannelMonitorAgent channelMonitorAgent; - private ChannelManager mockChannelManager; - - @Before - public void setUp() { - mockChannelManager = mock(ChannelManager.class); - Clock clock = Clock.fixed(CLOCK_INSTANT, ZoneId.systemDefault()); - ServiceDetails serviceDetails = new ServiceDetails.Builder().build(); - MessageFactory messageFactory = new MessageFactory(serviceDetails, clock, TestUtils.createMessageMapper()); - TimeoutManager mockTimeoutManager = mock(TimeoutManager.class); - MsbContextImpl msbContext = TestUtils.createMsbContextBuilder() - .withMessageFactory(messageFactory) - .withChannelManager(mockChannelManager) - .withClock(clock) - .withTimeoutManager(mockTimeoutManager) - .build(); - channelMonitorAgent = new DefaultChannelMonitorAgent(msbContext); - } - - @Test - public void testAnnounceProducerForServiceTopic() { - channelMonitorAgent.producerTopicCreated(TOPIC_ANNOUNCE); - - verify(mockChannelManager, never()).findOrCreateProducer(anyString(), any(RequestOptions.class)); - } - - @Test - public void testAnnounceProducerForNormalTopic() { - String topicName = "search:parsers:facets:v1"; - Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class))).thenReturn(mockProducer); - - // method under test - channelMonitorAgent.producerTopicCreated(topicName); - - // verify internal data structures - assertTrue(channelMonitorAgent.topicInfoMap.containsKey(topicName)); - assertTrue(channelMonitorAgent.topicInfoMap.get(topicName).isProducers()); - - Message message = verifyProducerInvokedAndReturnMessage(mockProducer); - verifyMessageContainsTopic(message, topicName); - } - - @Test - public void testAnnounceConsumerForServiceTopic() { - channelMonitorAgent.consumerTopicCreated(TOPIC_ANNOUNCE); - verify(mockChannelManager, never()).subscribe(anyString(), any(MessageHandler.class)); - } - - @Test - public void testAnnounceConsumerForNormalTopic() { - String topicName = "search:parsers:facets:v1"; - Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class))).thenReturn(mockProducer); - - // method under test - channelMonitorAgent.consumerTopicCreated(topicName); - - // verify internal data structures - assertTrue(channelMonitorAgent.topicInfoMap.containsKey(topicName)); - assertTrue(channelMonitorAgent.topicInfoMap.get(topicName).isConsumers()); - - Message message = verifyProducerInvokedAndReturnMessage(mockProducer); - verifyMessageContainsTopic(message, topicName); - } - - @Test - public void testRemoveConsumerForNormalTopic() { - String topicName = "search:parsers:facets:v1"; - Producer mockProducer = mock(Producer.class); - when(mockChannelManager.findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class))).thenReturn(mockProducer); - channelMonitorAgent.consumerTopicCreated(topicName); // subscribe to the topic as a preparation step - - // method under test - channelMonitorAgent.consumerTopicRemoved(topicName); - - assertTrue(channelMonitorAgent.topicInfoMap.containsKey(topicName)); - assertFalse(channelMonitorAgent.topicInfoMap.get(topicName).isConsumers()); - } - - @Test - public void testMessageProduce() { - String topicName = "search:parsers:facets:v1"; - - channelMonitorAgent.producerMessageSent(topicName); - - assertTrue(channelMonitorAgent.topicInfoMap.containsKey(topicName)); - assertEquals(CLOCK_INSTANT, channelMonitorAgent.topicInfoMap.get(topicName).getLastProducedAt()); - } - - @Test - public void testMessageConsumed() { - String topicName = "search:parsers:facets:v1"; - - channelMonitorAgent.consumerMessageReceived(topicName); - - assertTrue(channelMonitorAgent.topicInfoMap.containsKey(topicName)); - assertEquals(CLOCK_INSTANT, channelMonitorAgent.topicInfoMap.get(topicName).getLastConsumedAt()); - } - - @Test - public void testStart() { - ChannelMonitorAgent startedAgent = channelMonitorAgent.start(); - - assertSame(channelMonitorAgent, startedAgent); - verify(mockChannelManager).subscribe(eq(TOPIC_HEARTBEAT), any(MessageHandler.class)); - } - - private Message verifyProducerInvokedAndReturnMessage(Producer mockProducer) { - verify(mockChannelManager).findOrCreateProducer(eq(TOPIC_ANNOUNCE), any(RequestOptions.class)); - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); - verify(mockProducer).publish(messageCaptor.capture()); - return messageCaptor.getValue(); - } - - private void verifyMessageContainsTopic(Message message, String topicName) { - assertNotNull(message.getRawPayload()); - assertNotNull(message.getRawPayload().get("body")); - assertTrue(message.getRawPayload().get("body").has(topicName)); - } -} \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregatorTest.java b/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregatorTest.java deleted file mode 100644 index b25acef9..00000000 --- a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/DefaultChannelMonitorAggregatorTest.java +++ /dev/null @@ -1,304 +0,0 @@ -package io.github.tcdl.msb.monitor.aggregator; - -import static io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator.DEFAULT_HEARTBEAT_INTERVAL_MS; -import static io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator.DEFAULT_HEARTBEAT_TIMEOUT_MS; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import io.github.tcdl.msb.ChannelManager; -import io.github.tcdl.msb.MessageHandler; -import io.github.tcdl.msb.api.Callback; -import io.github.tcdl.msb.api.ObjectFactory; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.api.message.payload.RestPayload; -import io.github.tcdl.msb.api.monitor.AggregatorStats; -import io.github.tcdl.msb.api.monitor.AggregatorTopicStats; -import io.github.tcdl.msb.config.ServiceDetails; -import io.github.tcdl.msb.impl.MsbContextImpl; -import io.github.tcdl.msb.monitor.agent.AgentTopicStats; -import io.github.tcdl.msb.support.TestUtils; -import io.github.tcdl.msb.support.Utils; - -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import org.junit.Test; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; - -public class DefaultChannelMonitorAggregatorTest { - - private ChannelManager mockChannelManager = mock(ChannelManager.class); - private ObjectFactory mockObjectFactory = mock(ObjectFactory.class); - - private MsbContextImpl testMsbContext = TestUtils.createMsbContextBuilder() - .withObjectFactory(mockObjectFactory) - .withChannelManager(mockChannelManager) - .build(); - - private ScheduledExecutorService mockScheduledExecutorService = mock(ScheduledExecutorService.class); - @SuppressWarnings("unchecked") - private Callback mockHandler = mock(Callback.class); - private DefaultChannelMonitorAggregator channelMonitor = new DefaultChannelMonitorAggregator(testMsbContext, mockScheduledExecutorService, mockHandler); - - @Test - public void testOnAnnounce() { - String TOPIC1 = "topic1"; - String TOPIC2 = "topic2"; - String INSTANCE_ID = "instanceId"; - Message announcementMessage = createAnnouncementMessageWith2Topics(INSTANCE_ID, TOPIC1, TOPIC2); - - channelMonitor.onAnnounce(announcementMessage, null); - - assertEquals(1, channelMonitor.masterAggregatorStats.getServiceDetailsById().size()); - assertTrue(channelMonitor.masterAggregatorStats.getServiceDetailsById().containsKey(INSTANCE_ID)); - assertEquals(2, channelMonitor.masterAggregatorStats.getTopicInfoMap().size()); - - verify(mockHandler).call(channelMonitor.masterAggregatorStats); - } - - @Test - public void testOnHeartbeatResponses() { - String INSTANCE_ID_OBSOLETE = "instance_id_obsolete"; - String TOPIC_OBSOLETE = "topic_obsolete"; - - // Prepopulate stats to verify that obsolete data are cleared upon heartbeats - channelMonitor.masterAggregatorStats.getTopicInfoMap().put(TOPIC_OBSOLETE, new AggregatorTopicStats()); - channelMonitor.masterAggregatorStats.getServiceDetailsById().put(INSTANCE_ID_OBSOLETE, new ServiceDetails.Builder().build()); - - String INSTANCE_ID_1 = "instanceId1"; - String INSTANCE_ID_2 = "instanceId2"; - String TOPIC1 = "topic1"; - String TOPIC2 = "topic2"; - - Message hbMessage1 = createAnnouncementMessageWith2Topics(INSTANCE_ID_1, TOPIC1, TOPIC2); - Message hbMessage2 = createAnnouncementMessageWith2Topics(INSTANCE_ID_2, TOPIC1, TOPIC2); - - channelMonitor.onHeartbeatResponses(Arrays.asList(hbMessage1, hbMessage2)); - - assertEquals(2, channelMonitor.masterAggregatorStats.getServiceDetailsById().size()); - assertTrue(channelMonitor.masterAggregatorStats.getServiceDetailsById().containsKey(INSTANCE_ID_1)); - assertTrue(channelMonitor.masterAggregatorStats.getServiceDetailsById().containsKey(INSTANCE_ID_2)); - - assertEquals(2, channelMonitor.masterAggregatorStats.getTopicInfoMap().size()); - assertTrue(channelMonitor.masterAggregatorStats.getTopicInfoMap().containsKey(TOPIC1)); - assertTrue(channelMonitor.masterAggregatorStats.getTopicInfoMap().containsKey(TOPIC2)); - - verify(mockHandler, times(1)).call(channelMonitor.masterAggregatorStats); - } - - @Test - public void testAggregateInfo() { - String INSTANCE_ID = "instanceId"; - String TOPIC1 = "topic1"; - String TOPIC2 = "topic2"; - - Message announcementMessage = createAnnouncementMessageWith2Topics(INSTANCE_ID, TOPIC1, TOPIC2); - - AggregatorStats aggregatorStats = new AggregatorStats(); - channelMonitor.aggregateInfo(aggregatorStats, announcementMessage); - - assertEquals(1, aggregatorStats.getServiceDetailsById().size()); - assertTrue(aggregatorStats.getServiceDetailsById().containsKey(INSTANCE_ID)); - assertEquals(2, aggregatorStats.getTopicInfoMap().size()); - } - - @Test - public void testAggregateTopicStatsInitial() { - // Given - String INSTANCE_ID = "instanceId"; - String TOPIC_1 = "topic1"; - String TOPIC_2 = "topic2"; - Instant LAST_PRODUCED_AT = Instant.parse("2007-12-03T10:15:30.00Z"); - Instant LAST_CONSUMED_AT = Instant.parse("2007-12-03T10:15:32.00Z"); - - Map agentTopicStats = ImmutableMap.of( - TOPIC_1, - new AgentTopicStats() - .withProducers(true) - .withLastProducedAt(LAST_PRODUCED_AT), - - TOPIC_2, - new AgentTopicStats() - .withConsumers(true) - .withLastConsumedAt(LAST_CONSUMED_AT) - ); - - // When - AggregatorStats aggregatorStats = new AggregatorStats(); - channelMonitor.aggregateTopicStats(aggregatorStats, agentTopicStats, INSTANCE_ID); - - // Then - Map topicInfoMap = aggregatorStats.getTopicInfoMap(); - Map expectedTopicInfoMap = ImmutableMap.of( - TOPIC_1, - new AggregatorTopicStats() - .withProducers(ImmutableSet.of(INSTANCE_ID)) - .withConsumers(Collections.emptySet()) - .withLastProducedAt(LAST_PRODUCED_AT) - .withLastConsumedAt(null), - - TOPIC_2, - new AggregatorTopicStats() - .withProducers(Collections.emptySet()) - .withConsumers(ImmutableSet.of(INSTANCE_ID)) - .withLastProducedAt(null) - .withLastConsumedAt(LAST_CONSUMED_AT) - ); - - assertEquals(expectedTopicInfoMap, topicInfoMap); - } - - @Test - public void testAggregateTopicStatsServiceProducerInstances() { - String INSTANCE_ID_1 = "instanceId"; - String INSTANCE_ID_2 = "instanceId2"; - String TOPIC = "topic1"; - - AggregatorStats aggregatorStats = new AggregatorStats(); - // Process message from one instance - assertProducers(aggregatorStats, ImmutableSet.of(INSTANCE_ID_1), INSTANCE_ID_1, TOPIC); - // Process message from another instance - assertProducers(aggregatorStats, ImmutableSet.of(INSTANCE_ID_1, INSTANCE_ID_2), INSTANCE_ID_2, TOPIC); - // Process message from the first instance again - assertProducers(aggregatorStats, ImmutableSet.of(INSTANCE_ID_1, INSTANCE_ID_2), INSTANCE_ID_1, TOPIC); - - // Verify that consumers left untouched - assertTrue(aggregatorStats.getTopicInfoMap().get(TOPIC).getConsumers().isEmpty()); - } - - @Test - public void testAggregateTopicStatsServiceConsumerInstances() { - String INSTANCE_ID_1 = "instanceId"; - String INSTANCE_ID_2 = "instanceId2"; - String TOPIC = "topic1"; - - AggregatorStats aggregatorStats = new AggregatorStats(); - // Process message from one instance - assertConsumers(aggregatorStats, ImmutableSet.of(INSTANCE_ID_1), INSTANCE_ID_1, TOPIC); - // Process message from another instance - assertConsumers(aggregatorStats, ImmutableSet.of(INSTANCE_ID_1, INSTANCE_ID_2), INSTANCE_ID_2, TOPIC); - // Process message from the first instance again - assertConsumers(aggregatorStats, ImmutableSet.of(INSTANCE_ID_1, INSTANCE_ID_2), INSTANCE_ID_1, TOPIC); - - // Verify that consumers left untouched - assertTrue(aggregatorStats.getTopicInfoMap().get(TOPIC).getProducers().isEmpty()); - } - - @Test - public void testAggregateTopicStatsProducingTime() { - String INSTANCE_ID = "instanceId"; - String TOPIC = "topic1"; - Instant LAST_PRODUCED_AT_OLDER = Instant.parse("2007-12-03T10:15:30.00Z"); - Instant LAST_PRODUCED_AT_NEWER = Instant.parse("2007-12-03T10:15:31.00Z"); - Instant LAST_PRODUCED_AT_NEWEST = Instant.parse("2007-12-03T10:15:32.00Z"); - - AggregatorStats aggregatorStats = new AggregatorStats(); - assertLastProducedAt(aggregatorStats, LAST_PRODUCED_AT_NEWER, LAST_PRODUCED_AT_NEWER, INSTANCE_ID, TOPIC); - // Process message with newer date - assertLastProducedAt(aggregatorStats, LAST_PRODUCED_AT_NEWEST, LAST_PRODUCED_AT_NEWEST, INSTANCE_ID, TOPIC); - // Process message with older date - assertLastProducedAt(aggregatorStats, LAST_PRODUCED_AT_NEWEST, LAST_PRODUCED_AT_OLDER, INSTANCE_ID, TOPIC); - // Process message with null date - assertLastProducedAt(aggregatorStats, LAST_PRODUCED_AT_NEWEST, null, INSTANCE_ID, TOPIC); - } - - @Test - public void testAggregateTopicStatsConsumingTime() { - String INSTANCE_ID = "instanceId"; - String TOPIC = "topic1"; - Instant LAST_CONSUMED_AT_OLDER = Instant.parse("2007-12-03T10:15:30.00Z"); - Instant LAST_CONSUMED_AT_NEWER = Instant.parse("2007-12-03T10:15:31.00Z"); - Instant LAST_CONSUMED_AT_NEWEST = Instant.parse("2007-12-03T10:15:32.00Z"); - - AggregatorStats aggregatorStats = new AggregatorStats(); - assertLastConsumedAt(aggregatorStats, LAST_CONSUMED_AT_NEWER, LAST_CONSUMED_AT_NEWER, INSTANCE_ID, TOPIC); - // Process message with newer date - assertLastConsumedAt(aggregatorStats, LAST_CONSUMED_AT_NEWEST, LAST_CONSUMED_AT_NEWEST, INSTANCE_ID, TOPIC); - // Process message with older date - assertLastConsumedAt(aggregatorStats, LAST_CONSUMED_AT_NEWEST, LAST_CONSUMED_AT_OLDER, INSTANCE_ID, TOPIC); - // Process message with null date - assertLastConsumedAt(aggregatorStats, LAST_CONSUMED_AT_NEWEST, null, INSTANCE_ID, TOPIC); - } - - @Test - public void testStartWithHeartbeat() { - channelMonitor.start(true, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_HEARTBEAT_TIMEOUT_MS); - - verify(mockChannelManager).subscribe(eq(Utils.TOPIC_ANNOUNCE), any(MessageHandler.class)); - verify(mockScheduledExecutorService).scheduleAtFixedRate(any(HeartbeatTask.class), eq(0L), eq(DEFAULT_HEARTBEAT_INTERVAL_MS), - eq(TimeUnit.MILLISECONDS)); - } - - @Test - public void testStartWithoutHeartbeat() { - channelMonitor.start(false, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_HEARTBEAT_TIMEOUT_MS); - - verify(mockChannelManager).subscribe(eq(Utils.TOPIC_ANNOUNCE), any(MessageHandler.class)); - verifyNoMoreInteractions(mockScheduledExecutorService); - } - - @Test - public void testStop() { - channelMonitor.start(true, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_HEARTBEAT_TIMEOUT_MS); - channelMonitor.stop(); - - verify(mockChannelManager).unsubscribe(Utils.TOPIC_ANNOUNCE); - verify(mockScheduledExecutorService).shutdown(); - } - - private void assertProducers(AggregatorStats aggregatorStats, Set expectedProducers, String producerId, String topic) { - Map agentTopicStats = ImmutableMap.of(topic, new AgentTopicStats().withProducers(true)); - channelMonitor.aggregateTopicStats(aggregatorStats, agentTopicStats, producerId); - Map topicInfoMap = aggregatorStats.getTopicInfoMap(); - assertEquals(expectedProducers, topicInfoMap.get(topic).getProducers()); - } - - private void assertConsumers(AggregatorStats aggregatorStats, Set expectedConsumers, String consumerId, String topic) { - Map agentTopicStats = ImmutableMap.of(topic, new AgentTopicStats().withConsumers(true)); - channelMonitor.aggregateTopicStats(aggregatorStats, agentTopicStats, consumerId); - Map topicInfoMap = aggregatorStats.getTopicInfoMap(); - assertEquals(expectedConsumers, topicInfoMap.get(topic).getConsumers()); - } - - private void assertLastProducedAt(AggregatorStats aggregatorStats, Instant expectedlastProducedAt, Instant lastProducedAt, String instanceId, String topic) { - Map agentTopicStats = ImmutableMap.of(topic, new AgentTopicStats() - .withProducers(true) - .withLastProducedAt(lastProducedAt)); - channelMonitor.aggregateTopicStats(aggregatorStats, agentTopicStats, instanceId); - assertEquals(expectedlastProducedAt, aggregatorStats.getTopicInfoMap().get(topic).getLastProducedAt()); - } - - private void assertLastConsumedAt(AggregatorStats aggregatorStats, Instant expectedLastConsumedAt, Instant lastConsumedAt, String instanceId, String topic) { - Map agentTopicStats = ImmutableMap.of(topic, new AgentTopicStats() - .withConsumers(true) - .withLastConsumedAt(lastConsumedAt)); - channelMonitor.aggregateTopicStats(aggregatorStats, agentTopicStats, instanceId); - assertEquals(expectedLastConsumedAt, aggregatorStats.getTopicInfoMap().get(topic).getLastConsumedAt()); - } - - private Message createAnnouncementMessageWith2Topics(String instanceId, String topic1, String topic2) { - Map topicInfoMap = new HashMap<>(); - topicInfoMap.put(topic1, new AgentTopicStats().withProducers(true).withLastProducedAt(Instant.now())); - topicInfoMap.put(topic2, new AgentTopicStats().withConsumers(true).withLastConsumedAt(Instant.now())); - - RestPayload> announcementPayload = new RestPayload.Builder>() - .withBody(topicInfoMap) - .build(); - - return TestUtils.createMsbRequestMessage("to", instanceId, announcementPayload); - } - -} \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java b/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java deleted file mode 100644 index f1775c4d..00000000 --- a/core/src/test/java/io/github/tcdl/msb/monitor/aggregator/HeartbeatTaskTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.github.tcdl.msb.monitor.aggregator; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import io.github.tcdl.msb.api.Callback; -import io.github.tcdl.msb.api.MessageContext; -import io.github.tcdl.msb.api.ObjectFactory; -import io.github.tcdl.msb.api.RequestOptions; -import io.github.tcdl.msb.api.Requester; -import io.github.tcdl.msb.api.message.Message; -import io.github.tcdl.msb.api.message.payload.RestPayload; -import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; -import io.github.tcdl.msb.support.TestUtils; -import io.github.tcdl.msb.support.Utils; - -import java.util.List; -import java.util.function.BiConsumer; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import com.fasterxml.jackson.databind.JsonNode; - -public class HeartbeatTaskTest { - - private ObjectFactory mockObjectFactory = mock(ObjectFactory.class); - @SuppressWarnings("unchecked") - private Requester mockRequester = mock(Requester.class); - @SuppressWarnings("unchecked") - private Callback> mockMessageHandler = mock(Callback.class); - private HeartbeatTask heartbeatTask = new HeartbeatTask(ChannelMonitorAggregator.DEFAULT_HEARTBEAT_TIMEOUT_MS, mockObjectFactory, mockMessageHandler); - - @Before - public void setUp() { - when(mockObjectFactory.createRequester(anyString(), any(RequestOptions.class))).thenReturn(mockRequester); - @SuppressWarnings("unchecked") - BiConsumer responseHandler = any(BiConsumer.class); - when(mockRequester.onRawResponse(responseHandler)).thenReturn(mockRequester); - @SuppressWarnings("unchecked") - Callback endHandler = any(Callback.class); - when(mockRequester.onEnd(endHandler)).thenReturn(mockRequester); - } - - @SuppressWarnings("unchecked") - @Test - public void testRun() { - heartbeatTask.run(); - - ArgumentCaptor onResponseCaptor = ArgumentCaptor.forClass(BiConsumer.class); - ArgumentCaptor onEndCaptor = ArgumentCaptor.forClass(Callback.class); - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(List.class); - - verify(mockObjectFactory).createRequester(eq(Utils.TOPIC_HEARTBEAT), any(RequestOptions.class)); - verify(mockRequester).onRawResponse(onResponseCaptor.capture()); - verify(mockRequester).onEnd(onEndCaptor.capture()); - verify(mockRequester).publish(any(RestPayload.class)); - - // simulate incoming messages - Message msg1 = TestUtils.createSimpleRequestMessage("from:responder"); - Message msg2 = TestUtils.createSimpleRequestMessage("from:responder"); - onResponseCaptor.getValue().accept(msg1, null); - onResponseCaptor.getValue().accept(msg2, null); - onEndCaptor.getValue().call(null); - - verify(mockMessageHandler).call(messageCaptor.capture()); - - Assert.assertArrayEquals(new Message[] {msg1, msg2}, messageCaptor.getValue().toArray()); - } - - @Test - public void testRunWithException() { - try { - when(mockObjectFactory.createRequester(anyString(), any(RequestOptions.class))).thenThrow(new RuntimeException()); - heartbeatTask.run(); - } catch (Exception e) { - Assert.fail("Exception should not be thrown"); - } - } - -} \ No newline at end of file diff --git a/examples/pom.xml b/examples/pom.xml index 8c56bcb5..51ca696a 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,7 +3,7 @@ io.github.tcdl.msb msb-java - 1.5.3-SNAPSHOT + 1.6.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java b/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java index 2b35d82f..417c2d53 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java @@ -18,7 +18,6 @@ public class DateExtractor { public static void main(String... args) { MsbContext msbContext = new MsbContextBuilder() - .enableChannelMonitorAgent(true) .enableShutdownHook(true) .build(); new DateExtractor().start(msbContext); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java b/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java index a6e663b1..39c2da0b 100644 --- a/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java +++ b/examples/src/main/java/io/github/tcdl/msb/examples/FacetsAggregator.java @@ -23,7 +23,6 @@ public class FacetsAggregator { public static void main(String[] args) throws ScriptException, FileNotFoundException, NoSuchMethodException { MsbContext msbContext = new MsbContextBuilder() - .enableChannelMonitorAgent(true) .enableShutdownHook(true) .build(); diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/Monitor.java b/examples/src/main/java/io/github/tcdl/msb/examples/Monitor.java deleted file mode 100644 index 5378c95a..00000000 --- a/examples/src/main/java/io/github/tcdl/msb/examples/Monitor.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.tcdl.msb.examples; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JSR310Module; -import io.github.tcdl.msb.api.MsbContext; -import io.github.tcdl.msb.api.MsbContextBuilder; -import io.github.tcdl.msb.api.monitor.ChannelMonitorAggregator; -import io.github.tcdl.msb.support.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Monitor { - private static final Logger LOG = LoggerFactory.getLogger(Monitor.class); - - public static void main(String... args) { - MsbContext msbContext = new MsbContextBuilder() - .enableChannelMonitorAgent(false) - .enableShutdownHook(true) - .build(); - - ObjectMapper aggregatorStatsMapper = new ObjectMapper(); - aggregatorStatsMapper.registerModule(new JSR310Module()); - aggregatorStatsMapper.enable(SerializationFeature.INDENT_OUTPUT); - - ChannelMonitorAggregator channelMonitorAggregator = msbContext.getObjectFactory().createChannelMonitorAggregator(arg -> LOG.info("Received monitoring info " + Utils.toJson(arg, aggregatorStatsMapper))); - channelMonitorAggregator.start(); - } -} diff --git a/jmeter/pom.xml b/jmeter/pom.xml index 23f54a57..4b9e272b 100644 --- a/jmeter/pom.xml +++ b/jmeter/pom.xml @@ -6,7 +6,7 @@ io.github.tcdl.msb msb-java - 1.5.3-SNAPSHOT + 1.6.0-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index c669899d..114addd4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.tcdl.msb msb-java - 1.5.3-SNAPSHOT + 1.6.0-SNAPSHOT msb java msb java pom diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml index cb5b3e44..a1b76cfa 100644 --- a/spring-boot-starter/pom.xml +++ b/spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ io.github.tcdl.msb msb-java - 1.5.3-SNAPSHOT + 1.6.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java index 782c26b4..1e135320 100644 --- a/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java +++ b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java @@ -30,7 +30,6 @@ public MessageTemplate messageTemplate() { public MsbContext msbContext() { MsbContextBuilder builder = new MsbContextBuilder() - .enableChannelMonitorAgent(true) .withMsbConfig(msbConfig); if (messageGroupStrategy != null) From 35aba77f3a9e3223e4988d1fcfdf7707fee4059d Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 10 Nov 2016 15:35:46 +0200 Subject: [PATCH 166/226] remove cli module --- cli/README.md | 22 ---- cli/pom.xml | 57 ---------- .../tcdl/msb/cli/CliMessageHandler.java | 68 ------------ .../tcdl/msb/cli/CliMessageSubscriber.java | 32 ------ .../java/io/github/tcdl/msb/cli/CliTool.java | 95 ----------------- cli/src/main/resources/application.conf | 30 ------ cli/src/main/resources/logback.xml | 14 --- .../tcdl/msb/cli/CliMessageHandlerTest.java | 74 ------------- .../msb/cli/CliMessageSubscriberTest.java | 58 ---------- .../io/github/tcdl/msb/cli/CliToolTest.java | 100 ------------------ pom.xml | 1 - 11 files changed, 551 deletions(-) delete mode 100644 cli/README.md delete mode 100644 cli/pom.xml delete mode 100644 cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java delete mode 100644 cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java delete mode 100644 cli/src/main/java/io/github/tcdl/msb/cli/CliTool.java delete mode 100644 cli/src/main/resources/application.conf delete mode 100644 cli/src/main/resources/logback.xml delete mode 100644 cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java delete mode 100644 cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java delete mode 100644 cli/src/test/java/io/github/tcdl/msb/cli/CliToolTest.java diff --git a/cli/README.md b/cli/README.md deleted file mode 100644 index afea8bb3..00000000 --- a/cli/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# MSB-Java CLI - -Microservice bus - Java CLI - -## Build: -``` -mvn clean compile assembly:single -``` - -## Run: -``` -java -jar -``` - -## Usage - -Listens to a topic on the bus and prints JSON to stdout. By default it will also listen for response topics detected on messages, and JSON is pretty-printed. For [Newline-delimited JSON](http://en.wikipedia.org/wiki/Line_Delimited_JSON) compatibility, specify `-p false`. - -Options: -- **--topic** or **-t** -- **--follow** or **-f** listen for following topics, empty to disable (Default: response, ack) -- **--pretty** or **-p** set to false to use as a newline-delimited json stream, (Default: true) diff --git a/cli/pom.xml b/cli/pom.xml deleted file mode 100644 index eaebce95..00000000 --- a/cli/pom.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - io.github.tcdl.msb - msb-java - 1.5.3-SNAPSHOT - ../pom.xml - - 4.0.0 - msb-java-cli - msb java cli - msb java cli - jar - - scm:git:https://github.com/tcdl/msb-java.git - scm:git:git@github.com:tcdl/msb-java.git - https://github.com/tcdl/msb-java - HEAD - - - tcdl - https://github.com/tcdl - - - - io.github.tcdl.msb - msb-java-core - - - io.github.tcdl.msb - msb-java-amqp - - - ch.qos.logback - logback-classic - runtime - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - io.github.tcdl.msb.cli.CliTool - - - - jar-with-dependencies - - - - - - diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java deleted file mode 100644 index c0bf451e..00000000 --- a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.tcdl.msb.cli; - -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; -import io.github.tcdl.msb.api.exception.JsonConversionException; - -import java.io.IOException; -import java.util.List; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - -/** - * This handler dumps messages from the given topics. - * - * Additionally it examines the content of messages, looks for any other topics mentioned there (for example response topics) and subscribes to them as well. - */ -class CliMessageHandler implements ConsumerAdapter.RawMessageHandler { - private CliMessageSubscriber subscriber; - private boolean prettyOutput; - private List follow; - - public CliMessageHandler(CliMessageSubscriber subscriber, List follow, boolean prettyOutput) { - this.subscriber = subscriber; - this.follow = follow; - this.prettyOutput = prettyOutput; - } - - /** - * @throws JsonConversionException if some problems during parsing JSON - */ - @Override - public void onMessage(String jsonMessage, AcknowledgementHandlerInternal handler) { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - - if (prettyOutput) { - objectMapper.enable(SerializationFeature.INDENT_OUTPUT); - } - - try { - JsonNode jsonMessageObject = objectMapper.readTree(jsonMessage); - - // Subscribe to additional topics found in the message - if (jsonMessageObject.has("topics") - && jsonMessageObject.get("topics").has("response") - && jsonMessageObject.get("topics").get("response").isTextual() - && follow.contains("response")) { - - String responseTopicName = jsonMessageObject.get("topics").get("response").asText(); - - try { - subscriber.subscribe(responseTopicName, this); - } catch (Exception e) { - // Just ignore the exception - } - } - - // Reformat json message in pretty way - Object tmpObject = objectMapper.readValue(jsonMessage, Object.class); - System.out.println(objectMapper.writeValueAsString(tmpObject)); - } catch (IOException e) { - throw new JsonConversionException("Unable to process JSON", e); - } - } -} diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java deleted file mode 100644 index 78604207..00000000 --- a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.tcdl.msb.cli; - -import io.github.tcdl.msb.adapters.AdapterFactory; -import io.github.tcdl.msb.adapters.ConsumerAdapter; - -import java.util.HashSet; -import java.util.Set; - -/** - * {@link CliMessageSubscriber} uses raw {@link ConsumerAdapter} (that doesn't validate or parse incoming messages) - */ -public class CliMessageSubscriber { - private AdapterFactory adapterFactory; - private final Set registeredTopics = new HashSet<>(); - - public CliMessageSubscriber(AdapterFactory adapterFactory) { - this.adapterFactory = adapterFactory; - } - - /** - * Subscribes the given handler to the given topic - */ - public void subscribe(String topicName, CliMessageHandler handler) { - synchronized (registeredTopics) { - if (!registeredTopics.contains(topicName)) { - ConsumerAdapter adapter = adapterFactory.createConsumerAdapter(topicName, false); - adapter.subscribe(handler); - registeredTopics.add(topicName); - } - } - } -} diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliTool.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliTool.java deleted file mode 100644 index 625c9d64..00000000 --- a/cli/src/main/java/io/github/tcdl/msb/cli/CliTool.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.github.tcdl.msb.cli; - -import com.typesafe.config.ConfigFactory; -import io.github.tcdl.msb.adapters.AdapterFactoryLoader; -import io.github.tcdl.msb.config.MsbConfig; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * This tool allows to wiretap the given topics and log the messages from them. See {@link #printUsage()} for parameters supported. - */ -public class CliTool { - - public static void main(String[] args) { - MsbConfig msbConfig = new MsbConfig(ConfigFactory.load()); - AdapterFactoryLoader adapterFactoryLoader = new AdapterFactoryLoader(msbConfig); - - // Parse command line arguments - List topics = getTopics(args); - if (topics == null || topics.isEmpty()) { - // at least one topic is required - printUsage(); - return; - } - boolean prettyOutput = getPrettyOutput(args); - List follow = getFollow(args); - - CliMessageSubscriber subscriptionManager = new CliMessageSubscriber(adapterFactoryLoader.getAdapterFactory()); - subscribe(subscriptionManager, topics, follow, prettyOutput); - } - - private static void printUsage() { - System.out.println("Usage: CliTool <--topic|-t topic1,topic2> [--pretty true|false] [--follow response]\n" - + "--topic (required) specifies topic(s) to listen to\n" - + "--pretty (defaults to 'true') display formatted or not formatted messages\n" - + "--follow (defaults to 'response') allows to inspect incoming messages and subscribe to response topics automatically"); - } - - static void subscribe(CliMessageSubscriber subscriptionManager, List topics, List follow, boolean prettyOutput) { - for (String topicName : topics) { - subscriptionManager.subscribe(topicName, new CliMessageHandler(subscriptionManager, follow, prettyOutput)); - } - } - - static List getTopics(String[] args) { - return getOptionAsList(args, "--topic", "-t"); - } - - static List getFollow(String[] args) { - List follow = getOptionAsList(args, "--follow", "-f"); - if (follow == null || follow.isEmpty()) { - follow = Collections.singletonList("response"); - } - return follow; - } - - static boolean getPrettyOutput(String[] args) { - return getOptionAsBoolean(args, "--pretty", "-p"); - } - - static boolean getOptionAsBoolean(String[] args, String optionName, String alias) { - for (int i = 0; i < args.length; i++) { - String arg = args[i]; - - if (arg.equals(optionName) || arg.equals(alias)) { - if (i + 1 < args.length) { - String optionValue = args[i + 1]; - - return optionValue.equals("true"); - } - } - } - - return true; - } - - static List getOptionAsList(String[] args, String optionName, String alias) { - for (int i = 0; i < args.length; i++) { - String arg = args[i]; - - if (arg.equals(optionName) || arg.equals(alias)) { - if (i + 1 < args.length) { - String optionValue = args[i + 1]; - - return Arrays.asList(optionValue.split(",")); - } - } - } - - return null; - } - - } diff --git a/cli/src/main/resources/application.conf b/cli/src/main/resources/application.conf deleted file mode 100644 index 5e168911..00000000 --- a/cli/src/main/resources/application.conf +++ /dev/null @@ -1,30 +0,0 @@ -msbConfig { - - # Service Details - serviceDetails = { - name = "msb_java_cli" - version = "1.0.1" - instanceId = "msbjcli-ed59-4a39-9f95-811c5fb6ab87" - } - - brokerAdapterFactory = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory" - - # Thread pool used for scheduling ack and response timeout tasks - timerThreadPoolSize: 2 - - threadingConfig = { - consumerThreadPoolSize = 5 - # -1 means unlimited - consumerThreadPoolQueueCapacity = 20 - } - - # Broker Adapter Defaults - brokerConfig = { - host = "127.0.0.1" - port = "5672" - groupId = "cli-tool" - durable = false - prefetchCount = 0 - } - -} diff --git a/cli/src/main/resources/logback.xml b/cli/src/main/resources/logback.xml deleted file mode 100644 index 6e35c0af..00000000 --- a/cli/src/main/resources/logback.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - UTF-8 - %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n - - - - - - - - \ No newline at end of file diff --git a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java deleted file mode 100644 index 41704ae0..00000000 --- a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package io.github.tcdl.msb.cli; - -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import java.util.Collections; - -import org.junit.Test; - -public class CliMessageHandlerTest { - @Test - public void testSubscriptionToResponseQueue() { - CliMessageSubscriber subscriber = mock(CliMessageSubscriber.class); - - CliMessageHandler handler = new CliMessageHandler(subscriber, Collections.singletonList("response"), true); - handler.onMessage( - "{ \"topics\": {\n" - + " \"to\": \"search:parsers:facets:v1\",\n" - + " \"response\": \"search:parsers:facets:v1:response:3c3dec275b326c6500010843\"\n" - + " }}", null - ); - - verify(subscriber).subscribe("search:parsers:facets:v1:response:3c3dec275b326c6500010843", handler); - } - - @Test - public void testNoSubscriptionIfMissingResponseQueue() { - CliMessageSubscriber subscriber = mock(CliMessageSubscriber.class); - - CliMessageHandler handler = new CliMessageHandler(subscriber, Collections.singletonList("response"), true); - handler.onMessage( - "{ \"topics\": {\n" - + " \"to\": \"search:parsers:facets:v1\"\n" - + " }}", null - ); - - verifyNoMoreInteractions(subscriber); - } - - @Test - public void testNoSubscriptionIfNullResponseQueue() { - CliMessageSubscriber subscriber = mock(CliMessageSubscriber.class); - - CliMessageHandler handler = new CliMessageHandler(subscriber, Collections.singletonList("response"), true); - handler.onMessage( - "{ \"topics\": {\n" - + " \"to\": \"search:parsers:facets:v1\",\n" - + " \"response\": null\n" - + " }}", null - ); - - verifyNoMoreInteractions(subscriber); - } - - @Test - public void testSubscriptionNonExistingQueue() { - CliMessageSubscriber subscriber = mock(CliMessageSubscriber.class); - CliMessageHandler handler = new CliMessageHandler(subscriber, Collections.singletonList("response"), true); - - doThrow(new RuntimeException()).when(subscriber).subscribe("non-existent-queue", handler); - - handler.onMessage( - "{ \"topics\": {\n" - + " \"to\": \"search:parsers:facets:v1\",\n" - + " \"response\": \"non-existent-queue\"\n" - + " }}", null - ); - - // The point of this test is to verify that no exception is thrown in such case - // that's why we don't have any explicit assert or verification here - } -} \ No newline at end of file diff --git a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java deleted file mode 100644 index d49375c2..00000000 --- a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.tcdl.msb.cli; - -import io.github.tcdl.msb.adapters.AdapterFactory; -import io.github.tcdl.msb.adapters.ConsumerAdapter; -import org.junit.Before; -import org.junit.Test; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -public class CliMessageSubscriberTest { - - public static final String TOPIC_NAME = "topic1"; - - private AdapterFactory mockAdapterFactory; - private ConsumerAdapter mockConsumerAdapter; - private CliMessageHandler mockMessageHandler; - - private CliMessageSubscriber subscriptionManager; - - @Before - public void setUp() { - mockAdapterFactory = mock(AdapterFactory.class); - mockConsumerAdapter = mock(ConsumerAdapter.class); - when(mockAdapterFactory.createConsumerAdapter(TOPIC_NAME, false)).thenReturn(mockConsumerAdapter); - - mockMessageHandler = mock(CliMessageHandler.class); - - subscriptionManager = new CliMessageSubscriber(mockAdapterFactory); - } - - @Test - public void testInitialSubscriptionForTopic() { - testInitialSubscription(TOPIC_NAME); - } - - @Test - public void testDuplicateSubscription() { - testInitialSubscription(TOPIC_NAME); - - // make another subscription to the same topic - subscriptionManager.subscribe(TOPIC_NAME, mockMessageHandler); - - // verify that nothing happens - verifyNoMoreInteractions(mockAdapterFactory); - verifyNoMoreInteractions(mockAdapterFactory); - } - - private void testInitialSubscription(String topicName) { - // method under test - subscriptionManager.subscribe(topicName, mockMessageHandler); - - verify(mockAdapterFactory).createConsumerAdapter(topicName, false); - verify(mockConsumerAdapter).subscribe(mockMessageHandler); - } -} \ No newline at end of file diff --git a/cli/src/test/java/io/github/tcdl/msb/cli/CliToolTest.java b/cli/src/test/java/io/github/tcdl/msb/cli/CliToolTest.java deleted file mode 100644 index ffff2540..00000000 --- a/cli/src/test/java/io/github/tcdl/msb/cli/CliToolTest.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.github.tcdl.msb.cli; - -import org.junit.Test; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class CliToolTest { - @Test - public void testStrToArr() { - String[] args = strToArr("1 2 3"); - assertArrayEquals(new String[] { "1", "2", "3" }, args); - - args = strToArr("--topic search:parsers:facets:v1"); - assertArrayEquals(new String[] {"--topic", "search:parsers:facets:v1"}, args); - } - - @Test - public void testGetOptionAsBoolean() { - boolean optionValue = CliTool.getOptionAsBoolean(strToArr("--pretty true"), "--pretty", "-p"); - assertTrue(optionValue); - - optionValue = CliTool.getOptionAsBoolean(strToArr("-p false"), "--pretty", "-p"); - assertFalse(optionValue); - - optionValue = CliTool.getOptionAsBoolean(strToArr("-p blah"), "--pretty", "-p"); - assertFalse(optionValue); - } - - @Test - public void testGetOptionAsListSingleValue() { - List optionValues = CliTool.getOptionAsList(strToArr("--topic search:parsers:facets:v1"), "--topic", "-t"); - assertNotNull(optionValues); - assertEquals(1, optionValues.size()); - assertTrue(optionValues.contains("search:parsers:facets:v1")); - } - - @Test - public void testGetOptionAsListMultipleValues() { - List optionValues = CliTool.getOptionAsList(strToArr("--topic search:parsers:facets:v1,search:packages:v1"), "--topic", "-t"); - assertNotNull(optionValues); - assertEquals(2, optionValues.size()); - assertTrue(optionValues.contains("search:parsers:facets:v1")); - assertTrue(optionValues.contains("search:packages:v1")); - } - - @Test - public void testGetOptionAsListNoValues() { - List optionValues = CliTool.getOptionAsList(strToArr("a b c"), "--topic", "-t"); - assertNull(optionValues); - } - - @Test - public void testGetFollowDefaultValue() { - List follow = CliTool.getFollow(strToArr("--topic search:parsers:facets:v1")); - assertNotNull(follow); - assertEquals(1, follow.size()); - assertTrue(follow.contains("response")); - } - - @Test - public void testGetTopicsDefaultValue() { - List topics = CliTool.getTopics(strToArr("--follow response,ack")); - assertNull(topics); - } - - @Test - public void getPrettyOutputDefaultValue() { - boolean prettyOutput = CliTool.getPrettyOutput(strToArr("--follow response,ack")); - assertTrue(prettyOutput); - } - - @Test - public void testSubscribe() { - CliMessageSubscriber subscriptionManager = mock(CliMessageSubscriber.class); - List topics = Arrays.asList("topic1", "topic2"); - List follow = Collections.singletonList("response"); - - CliTool.subscribe(subscriptionManager, topics, follow, false); - - verify(subscriptionManager).subscribe(eq("topic1"), any(CliMessageHandler.class)); - verify(subscriptionManager).subscribe(eq("topic2"), any(CliMessageHandler.class)); - } - - private String[] strToArr(String argString) { - return argString.split(" "); - } -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index c669899d..029aca7b 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,6 @@ core amqp - cli acceptance jmeter examples From 662f2b870ae5673b638b5d57174d5d0ac22525b3 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 11 Nov 2016 11:21:55 +0200 Subject: [PATCH 167/226] Added unit test to improve coverage --- .../amqp/AmqpProducerAdapterTest.java | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java index 9bbb71fb..b163a5c2 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapterTest.java @@ -18,11 +18,10 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class AmqpProducerAdapterTest { + public static final String TOPIC_NAME = "myTopic"; private Channel mockChannel; private AmqpConnectionManager mockAmqpConnectionManager; private AmqpBrokerConfig mockAmqpBrokerConfig; @@ -42,41 +41,43 @@ public void setUp() throws IOException { @Test public void testExchangeWithCorrectTypeCreated() throws IOException { - String topicName = "myTopic"; + new AmqpProducerAdapter(TOPIC_NAME, ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager); + verify(mockChannel).exchangeDeclare(TOPIC_NAME, "fanout", false, true, null); - new AmqpProducerAdapter(topicName, ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager); - verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null); - - new AmqpProducerAdapter(topicName, ExchangeType.TOPIC, mockAmqpBrokerConfig, mockAmqpConnectionManager); - verify(mockChannel).exchangeDeclare(topicName, "topic", false, true, null); + new AmqpProducerAdapter(TOPIC_NAME, ExchangeType.TOPIC, mockAmqpBrokerConfig, mockAmqpConnectionManager); + verify(mockChannel).exchangeDeclare(TOPIC_NAME, "topic", false, true, null); } @Test(expected = RuntimeException.class) public void testInitializationError() throws IOException { when(mockChannel.exchangeDeclare(anyString(), anyString(), anyBoolean(), anyBoolean(), any())).thenThrow(new IOException()); - new AmqpProducerAdapter("myTopic", ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager); + new AmqpProducerAdapter(TOPIC_NAME, ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager); } @Test public void testPublish() throws ChannelException, IOException { - String topicName = "myTopic"; String message = "message"; - AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager); - + AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(TOPIC_NAME, ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager); producerAdapter.publish(message); + verify(mockChannel).basicPublish(TOPIC_NAME, StringUtils.EMPTY, MessageProperties.PERSISTENT_BASIC, message.getBytes()); + } - verify(mockChannel).basicPublish(topicName, StringUtils.EMPTY, MessageProperties.PERSISTENT_BASIC, message.getBytes()); + @Test(expected = ChannelException.class) + public void testPublishExceptionally() throws Exception { + String message = "message"; + AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(TOPIC_NAME, ExchangeType.FANOUT, mockAmqpBrokerConfig, mockAmqpConnectionManager); + doThrow(new RuntimeException()).when(mockChannel).basicPublish(anyString(), anyString(), any(AMQP.BasicProperties.class), any(byte[].class)); + producerAdapter.publish(message); } @Test public void testPublishWithRoutingKey() throws Exception{ - String topicName = "myTopic"; String message = "message"; String routingKey = "routingKey"; - AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(topicName, ExchangeType.TOPIC, mockAmqpBrokerConfig, mockAmqpConnectionManager); + AmqpProducerAdapter producerAdapter = new AmqpProducerAdapter(TOPIC_NAME, ExchangeType.TOPIC, mockAmqpBrokerConfig, mockAmqpConnectionManager); producerAdapter.publish(message, routingKey); - verify(mockChannel).basicPublish(topicName, routingKey, MessageProperties.PERSISTENT_BASIC, message.getBytes()); + verify(mockChannel).basicPublish(TOPIC_NAME, routingKey, MessageProperties.PERSISTENT_BASIC, message.getBytes()); } @Test From f13802d315486ab927f15030c854e7d32ff5eb87 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Wed, 16 Nov 2016 12:16:57 +0200 Subject: [PATCH 168/226] msb-java 1.6.0 release preparation --- cli/pom.xml | 0 doc/MSB.md | 13 ++++--------- release-notes.html | 29 +++++++++++++++++------------ 3 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 cli/pom.xml diff --git a/cli/pom.xml b/cli/pom.xml deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/MSB.md b/doc/MSB.md index 5a0bad6e..17983cc7 100644 --- a/doc/MSB.md +++ b/doc/MSB.md @@ -136,8 +136,10 @@ MSB-Java has pluggable architecture that allows to use different bus adapters tr It consists of the following Maven modules: - msb-java-core: core classes - msb-java-amqp: AMQP adapter that allow to use AMQP broker (for example RabbitMQ) as a bus -- msb-java-cli: CLI monitoring tool +- msb-spring-boot-starter: Spring Boot auto-configuration classes +- msb-java-acceptance: acceptance tests - msb-java-examples: examples of various microservices +- ApacheJmeter_msb: MSB JMeter Sampler ## Core classes @@ -247,11 +249,6 @@ To launch those you need to have RabbitMQ up and running. Also you have to start Another caveat is that the business logic of `PongService` (passed as lambda in the last argument during `ResponderServer` creation) can be invoked from different threads concurrently. So the lambda should be __thread-safe__. The same applies to the lambda passed as an argument to `onResponse` in `PingService`. -## CLI tool - -See [Readme for CLI tool](/cli/README.md). - - ## Test support - mocking approaches. See [MSB-Java mocking support](MSB-TEST-MOCKING.md) @@ -280,14 +277,12 @@ See [reference.conf example](/core/src/main/resources/reference.conf) _application.conf_ – applications should provide an application.conf with any settings specific for this particular application. Overrides values from _reference.conf_. -See [application.conf example](/cli/src/main/resources/application.conf) - All configuration files use _key-value pair_ structure. ### Configuration files hierarchy - [amqp.conf](/amqp/src/main/resources/amqp.conf) - [reference.conf](/core/src/main/resources/reference.conf) - overrides values from amqp.conf -- [application.conf](/cli/src/main/resources/application.conf) - overrides values from reference.conf +- [application.conf](/acceptance/src/main/resources/application.conf) - overrides values from reference.conf ### Description of MSB configuration fields Service details section describes microservice parameters. diff --git a/release-notes.html b/release-notes.html index 642e9a9c..f88514d4 100644 --- a/release-notes.html +++ b/release-notes.html @@ -4,26 +4,31 @@ -

Welcome to MSB-Java version 1.5.2

+

Welcome to MSB-Java version 1.6.0

-

October 3, 2016

+

November 16, 2016

-Changes in MSB-Java version 1.6.0 (not yet released)
+Features of MSB-Java version 1.6.0:
    - Broken backward compatibility with 1.5.2 version. Now exchange type for AMQ is not determined by routing key
-    presence/absence. It can be set explicitly using AmqpRequestOptions. Default value 'fanout' is used if exchange
-    type is not specified. ResponderServer mirrors this behavior using AmqpResponderOptions (see examples for routing key)
-   - Fixed possible deadlock in AMQP channel automatic recovery logic.
+     presence/absence. It can be set explicitly using AmqpRequestOptions. Default value 'fanout' is used if exchange
+     type is not specified. ResponderServer mirrors this behavior using AmqpResponderOptions (see examples for routing key);
+   - Fixed possible deadlock in AMQP channel automatic recovery logic;
+   - Added Spring Boot auto-configuration spring-boot-starter module;
+   - MSB_BROKER_VIRTUAL_HOST environment variable was deprecated. MSB_BROKER_AMQP_VHOST environment variable is used for 
+     RabbitMQ Virutal host specification. The both variable are supported now. Deprecated will be removed in next release;
+   - Removed ChannelMonitorAgent and ChannelMonitorAggregator;
+   - Removed CLI module.
 
 Features of MSB-Java version 1.5.2:
-   - Added routing key support to API and AMQP adapter
-   - Added "routingKey" field to message envelope "topics" section
-   - Removed confusing response topic for cases when no responses/acks are expected
+   - Added routing key support to API and AMQP adapter;
+   - Added "routingKey" field to message envelope "topics" section;
+   - Removed confusing response topic for cases when no responses/acks are expected.
 
 Features of MSB-Java version 1.5.1:
-   - Added stop() method to ResponderServer
-   - Add request object and map into MsbThreadContext
-   - Fixed broken graceful shutdown of TimeoutManager on MsbContextImpl shutdown
+   - Added stop() method to ResponderServer;
+   - Add request object and map into MsbThreadContext;
+   - Fixed broken graceful shutdown of TimeoutManager on MsbContextImpl shutdown.
 
 Features of MSB-Java version 1.5.0:
    - Multithreading settings parameters consumerThreadPoolSize, consumerThreadPoolQueueCapacity moved

From 6a934f737d2e0c47cd1d03af3523b5bfd42832b3 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Wed, 16 Nov 2016 14:19:02 +0200
Subject: [PATCH 169/226] [maven-release-plugin] prepare release msb-java-1.6.0

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 5 ++---
 7 files changed, 14 insertions(+), 15 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index ddf56692..861929c3 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.0-SNAPSHOT
+        1.6.0
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.0
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index ef791945..3e26030c 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.0-SNAPSHOT
+        1.6.0
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.0
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 205ed0be..6bc0e358 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.0-SNAPSHOT
+        1.6.0
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.0
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 51ca696a..df9865e4 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.0-SNAPSHOT
+        1.6.0
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.0
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 4b9e272b..906877ee 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.0-SNAPSHOT
+        1.6.0
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.0
     
 
     
diff --git a/pom.xml b/pom.xml
index bab6f506..f85622d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.0-SNAPSHOT
+    1.6.0
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.0
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index a1b76cfa..80ca6795 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -1,10 +1,9 @@
 
-
+
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.0-SNAPSHOT
+		1.6.0
 		../pom.xml
 	
 	4.0.0

From 9831ba77150a6e93856826d38e49941394412417 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Wed, 16 Nov 2016 14:19:12 +0200
Subject: [PATCH 170/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 2 +-
 7 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 861929c3..2ae38817 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.0
+        1.6.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.0
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 3e26030c..dad97e98 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.0
+        1.6.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.0
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 6bc0e358..f670a88c 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.0
+        1.6.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.0
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index df9865e4..fee0f2b4 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.0
+        1.6.1-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.0
+        HEAD
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 906877ee..ce2401e0 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.0
+        1.6.1-SNAPSHOT
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.0
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index f85622d7..636f949e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.0
+    1.6.1-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.0
+        HEAD
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index 80ca6795..6ba70310 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.0
+		1.6.1-SNAPSHOT
 		../pom.xml
 	
 	4.0.0

From 4f402af41e9a036a564360794af669076025a16f Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Wed, 16 Nov 2016 15:30:46 +0200
Subject: [PATCH 171/226] Added SCM supportAdded SCM support

---
 spring-boot-starter/pom.xml | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index 6ba70310..d0a0a048 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -11,6 +11,18 @@
 	msb spring boot starter
 	jar
 
+	
+		scm:git:https://github.com/tcdl/msb-java.git
+		scm:git:git@github.com:tcdl/msb-java.git
+		https://github.com/tcdl/msb-java
+		HEAD
+	
+
+	
+		tcdl
+		https://github.com/tcdl
+	
+
 	
 		UTF-8
 		UTF-8

From 0ef23c7ede9914fe96dcea33f995edcfed62f35e Mon Sep 17 00:00:00 2001
From: sergiv83 
Date: Fri, 18 Nov 2016 13:48:57 +0200
Subject: [PATCH 172/226] add documentation for msb-spring-boot-starter

---
 doc/MSB.md | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/doc/MSB.md b/doc/MSB.md
index 17983cc7..6c7bbdba 100644
--- a/doc/MSB.md
+++ b/doc/MSB.md
@@ -395,6 +395,28 @@ More references on how to configure the broker to allow the remote access with t
 
 `prefetchCount` - Specify the limit number of unacknowledged messages on a channel when consuming. Value of 0 stands for unlimited. The default value is 10.
 
+###Autoconfiguration for Srping Boot
+Integration with Spring Boot has been improved by adding an [autoconfiguration module](https://github.com/tcdl/msb-java/tree/master/spring-boot-starter). If your application is based on Spring Boot, this module can simplify the usage of msb-java. Using this type of connection msb to your project you'll get thinner dependency list, preconfigured spring beans in your application context and no need to write a single line of configuration (presuming that you have rabbitmq on your local machine with all default values).
+####How to start
+If you use maven, just add the following dependency:
+```xml
+	
+        io.github.tcdl.msb
+        msb-spring-boot-starter
+        ${msb.version}
+    
+```
+In this case, you'll get MsbConfig, MessageTemplate and MsbContext beans ready to work with your local instance of integration bus (rabbitmq).
+####Customization
+You can use the full power of spring configuration with msb-spring-boot-starter. Complete list of configuration ways can be found in [Spring Boot documentation](http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config). The idea lies in the fact that we have a simple mapping from [typesafe configuration parameters](https://github.com/tcdl/msb-java/blob/master/core/src/main/resources/reference.conf) to spring ones. Thus msbConfig{...serviceDetails={name=xxx}} will be translated into spring config property msbConfig.serviceDetails.name and can be set in application.yml or .properties or passed as a command line argument or set as an environment variable MSB_CONFIG_SERVICE_DETAILS_NAME and so on. Another aspect of configuration is using your instances of MessageGroupStrategy, MessageTemplate or MsbContext. Each of these beans is present in context but uses @ConditionalOnMissingBean annotation. So, your instances will be used if they appear in spring context.
+####Overridden default values
+Some of the default values has been overridden in this module to not bring unexpected side effects to projects which already use msb-java without autoconfiguration. 
+|value                             |reference|autoconfigure|
+|:--------------------------------:|:-------:|:-----------:|
+|msbConfig.serviceDetails.name     |required |random value |
+|msbConfig.serviceDetails.version  |required |1.0.0        |
+|msbProperties.brokerConfig.durable|false    |true         |
+|msbConfig.timerThreadPoolSize     |10       |2            |
 
 ## AMQP adapter
 

From 0fc6cd17289daa9b393f902b708bab2e40f439c0 Mon Sep 17 00:00:00 2001
From: alex 
Date: Tue, 29 Nov 2016 17:19:51 +0200
Subject: [PATCH 173/226] Removed blocking_requester.story, DateExtractor and
 couple other things. Fixed handling of request timeout from ack.

---
 .../msb/acceptance/BlockingRequesterTest.java | 115 +++++++++++++
 .../msb/acceptance/bdd/ScenarioRunner.java    |   2 -
 .../bdd/steps/AsyncRequesterSteps.java        |  48 ------
 .../bdd/steps/ConfigurationSteps.java         |  17 +-
 .../msb/acceptance/bdd/steps/MsbSteps.java    |  55 ------
 .../bdd/steps/RequesterResponderSteps.java    |  96 +----------
 .../scenarios/blocking_requester.story        |  37 ----
 .../resources/scenarios/date_extractor.story  |  22 ---
 .../multiple_requests_to_date_extractor.story |  15 --
 .../github/tcdl/msb/collector/Collector.java  |   3 +
 .../tcdl/msb/collector/CollectorTest.java     |  25 +++
 .../tcdl/msb/examples/DateExtractor.java      | 160 ------------------
 .../tcdl/msb/examples/DateExtractorUtils.java |  24 ---
 .../msb/examples/DateExtractorUtilsTest.java  |  78 ---------
 14 files changed, 151 insertions(+), 546 deletions(-)
 create mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/BlockingRequesterTest.java
 delete mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/AsyncRequesterSteps.java
 delete mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/MsbSteps.java
 delete mode 100644 acceptance/src/test/resources/scenarios/blocking_requester.story
 delete mode 100644 acceptance/src/test/resources/scenarios/date_extractor.story
 delete mode 100644 acceptance/src/test/resources/scenarios/multiple_requests_to_date_extractor.story
 delete mode 100644 examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java
 delete mode 100644 examples/src/main/java/io/github/tcdl/msb/examples/DateExtractorUtils.java
 delete mode 100644 examples/src/test/java/io/github/tcdl/msb/examples/DateExtractorUtilsTest.java

diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/BlockingRequesterTest.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/BlockingRequesterTest.java
new file mode 100644
index 00000000..0104edad
--- /dev/null
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/BlockingRequesterTest.java
@@ -0,0 +1,115 @@
+package io.github.tcdl.msb.acceptance;
+
+import io.github.tcdl.msb.api.MsbContext;
+import io.github.tcdl.msb.api.MsbContextBuilder;
+import io.github.tcdl.msb.api.RequestOptions;
+import io.github.tcdl.msb.api.ResponderOptions;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.*;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+
+public class BlockingRequesterTest {
+
+    private static final String REQUEST_NAMESPACE = "test:future";
+    private static final String REQUEST_BODY = "Hi there";
+    private static final String RESPONSE_BODY = "Hello, Future!";
+
+    private MsbContext msbContext;
+
+    @Before
+    public void setUp() throws Exception {
+        msbContext = new MsbContextBuilder().build();
+    }
+
+    @Test
+    public void singleFutureResponse() throws Exception {
+
+        //set up responder
+        msbContext.getObjectFactory()
+                .createResponderServer(REQUEST_NAMESPACE, ResponderOptions.DEFAULTS, (request, responderContext) -> {
+                    if (REQUEST_BODY.equals(request)) {
+                        responderContext.getResponder().send(RESPONSE_BODY);
+                    } else {
+                        responderContext.getResponder().sendAck(0, 0);
+                    }
+                }, String.class)
+                .listen();
+
+        //prepare and send request
+        int timeoutMs = 2000;
+        RequestOptions requestOptions = new RequestOptions.Builder().withResponseTimeout(timeoutMs).build();
+
+        CompletableFuture futureResponse = msbContext.getObjectFactory()
+                .createRequesterForSingleResponse(REQUEST_NAMESPACE, String.class, requestOptions)
+                .request(REQUEST_BODY);
+
+        //wait for response
+        try {
+            String actualResponseBody = futureResponse.get(timeoutMs, TimeUnit.MILLISECONDS);
+            assertEquals("Unexpected response body", RESPONSE_BODY, actualResponseBody);
+        } catch (Exception e) {
+            fail("Response was not received in time: " + e);
+        }
+    }
+
+    @Test
+    public void singleFutureResponseAfterAcknowledge() throws Exception {
+
+        int newTimeout = 500;
+        int initialTimeout = 200;
+
+        //set up responder
+        msbContext.getObjectFactory().createResponderServer(REQUEST_NAMESPACE, ResponderOptions.DEFAULTS, (request, responderContext) -> {
+            //send ack requesting additional time for response generation, wait, send response
+            responderContext.getResponder().sendAck(newTimeout, 1);
+            TimeUnit.MILLISECONDS.sleep(newTimeout / 2);
+            responderContext.getResponder().send(RESPONSE_BODY);
+        }, String.class)
+                .listen();
+
+        //prepare and send request
+        RequestOptions requestOptions = new RequestOptions.Builder().withResponseTimeout(initialTimeout).build();
+        CompletableFuture futureResponse = msbContext.getObjectFactory()
+                .createRequesterForSingleResponse(REQUEST_NAMESPACE, String.class, requestOptions)
+                .request(REQUEST_BODY);
+
+        //wait for response
+        try {
+            String actualResponseBody = futureResponse.get(newTimeout, TimeUnit.MILLISECONDS);
+            assertEquals("Unexpected response body", RESPONSE_BODY, actualResponseBody);
+        } catch (Exception e) {
+            fail("Response was not received in time: " + e);
+        }
+    }
+
+    @Test(timeout = 5000, expected = CancellationException.class)
+    public void tooManyRemainingResponses() throws Exception {
+
+        //set up responder
+        msbContext.getObjectFactory().createResponderServer(REQUEST_NAMESPACE, ResponderOptions.DEFAULTS, (request, responderContext) -> {
+            //send ack requesting additional time for response generation, wait, send response
+            responderContext.getResponder().sendAck(null, 2); //oops
+            responderContext.getResponder().send(RESPONSE_BODY);
+        }, String.class)
+                .listen();
+
+        //prepare and send request
+        CompletableFuture futureResponse = msbContext.getObjectFactory()
+                .createRequesterForSingleResponse(REQUEST_NAMESPACE, String.class, RequestOptions.DEFAULTS)
+                .request(REQUEST_BODY);
+
+        //wait for response
+        futureResponse.get();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        msbContext.shutdown();
+    }
+}
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/ScenarioRunner.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/ScenarioRunner.java
index 51d33594..94f77629 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/ScenarioRunner.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/ScenarioRunner.java
@@ -1,6 +1,5 @@
 package io.github.tcdl.msb.acceptance.bdd;
 
-import io.github.tcdl.msb.acceptance.bdd.steps.AsyncRequesterSteps;
 import io.github.tcdl.msb.acceptance.bdd.steps.ConfigurationSteps;
 import io.github.tcdl.msb.acceptance.bdd.steps.LoggerSteps;
 import io.github.tcdl.msb.acceptance.bdd.steps.RequesterResponderSteps;
@@ -28,7 +27,6 @@ public List getStepInstances() {
         steps.addAll(Arrays.asList(
                 new ConfigurationSteps(),
                 new RequesterResponderSteps(),
-                new AsyncRequesterSteps(),
                 new LoggerSteps()
         ));
         return steps;
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/AsyncRequesterSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/AsyncRequesterSteps.java
deleted file mode 100644
index 1623136a..00000000
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/AsyncRequesterSteps.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package io.github.tcdl.msb.acceptance.bdd.steps;
-
-import io.github.tcdl.msb.api.Requester;
-import io.github.tcdl.msb.api.message.payload.RestPayload;
-import org.jbehave.core.annotations.Given;
-import org.jbehave.core.annotations.Then;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Steps to send multiple requests
- */
-public class AsyncRequesterSteps extends MsbSteps {
-
-    private CountDownLatch await;
-
-    @Given("$numberOfRequesters requesters send a request to namespace $namespace with query '$query'")
-    public void sendRequests(int numberOfRequesters, String namespace, String query) throws Exception {
-        await = new CountDownLatch(numberOfRequesters);
-
-        for (int i = 0; i < numberOfRequesters; i++) {
-            CompletableFuture.supplyAsync(() -> {
-                Requester requester = helper.createRequester(namespace, 1, RestPayload.class);
-                try {
-                    RestPayload requestPayload = helper.createFacetParserPayload(query, null);
-                    helper.sendRequest(requester, requestPayload, true, 1, null, this::onResponse);
-                } catch (Exception e) {
-                    System.err.println(e.getMessage());
-                }
-                return null;
-             });
-        }
-    }
-
-    @Then("wait responses in $timeout ms")
-    public void waitForResponse(long timeout) throws Exception {
-        await.await(timeout, TimeUnit.MILLISECONDS);
-        assertEquals("Some requests were not responded", 0, await.getCount());
-    }
-
-    private void onResponse(RestPayload payload) {
-        await.countDown();
-    }
-}
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
index f130166e..00792ae1 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
@@ -3,6 +3,7 @@
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigValueFactory;
+import io.github.tcdl.msb.acceptance.MsbTestHelper;
 import org.jbehave.core.annotations.Given;
 import org.jbehave.core.annotations.Then;
 import org.jbehave.core.annotations.When;
@@ -10,16 +11,16 @@
 /**
  * Steps to manipulate with MSB configuration
  */
-public class ConfigurationSteps extends MsbSteps {
+public class ConfigurationSteps {
+
+    protected final MsbTestHelper helper = MsbTestHelper.getInstance();
 
     private String MSB_CONFIG_ROOT = "msbConfig";
     private String VALIDATE_MESSAGE = MSB_CONFIG_ROOT + ".validateMessage";
-    private String TIME_THREAD_POOL_SIZE = MSB_CONFIG_ROOT + ".timerThreadPoolSize";
 
     private String MSB_BROKER_CONFIG_ROOT = "msbConfig.brokerConfig";
     private String MSB_THREADING_CONFIG_ROOT = "msbConfig.threadingConfig";
     private String MSB_BROKER_CONSUMER_THREAD_POOL_SIZE = MSB_THREADING_CONFIG_ROOT + ".consumerThreadPoolSize";
-    private String MSB_BROKER_CONSUMER_THREAD_POOL_QUEUE_CAPACITY = MSB_BROKER_CONFIG_ROOT + ".consumerThreadPoolQueueCapacity";
     private String MSB_BROKER_CONSUMER_THREAD_POOL_PREFETCH_COUNT = MSB_BROKER_CONFIG_ROOT + ".prefetchCount";
 
     private Config config = ConfigFactory.load();
@@ -29,21 +30,11 @@ public void initWithValidateMessage(boolean validate) {
         config = config.withValue(VALIDATE_MESSAGE, ConfigValueFactory.fromAnyRef(validate));
     }
 
-    @Given("MSB configuration with timer thread pool size $size")
-    public void initWithTimerThreadPoolSize(int size) {
-        config = config.withValue(TIME_THREAD_POOL_SIZE, ConfigValueFactory.fromAnyRef(size));
-    }
-
     @Given("MSB configuration with consumer thread pool size $size")
     public void initWithConsumerThreadPoolSize(int size) {
         config = config.withValue(MSB_BROKER_CONSUMER_THREAD_POOL_SIZE, ConfigValueFactory.fromAnyRef(size));
     }
 
-    @Given("MSB configuration with consumer thread pool queue capacity $capacity")
-    public void initWithConsumerThreadPoolQueueCapacity(int capacity) {
-        config = config.withValue(MSB_BROKER_CONSUMER_THREAD_POOL_QUEUE_CAPACITY, ConfigValueFactory.fromAnyRef(capacity));
-    }
-
     @Given("MSB configuration with consumer prefetch count $count")
     public void initWithConsumerPrefetchCount(int capacity) {
         config = config.withValue(MSB_BROKER_CONSUMER_THREAD_POOL_PREFETCH_COUNT, ConfigValueFactory.fromAnyRef(capacity));
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/MsbSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/MsbSteps.java
deleted file mode 100644
index cb7d7199..00000000
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/MsbSteps.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package io.github.tcdl.msb.acceptance.bdd.steps;
-
-import io.github.tcdl.msb.acceptance.MsbTestHelper;
-import io.github.tcdl.msb.api.MsbContext;
-import org.jbehave.core.annotations.Given;
-import org.jbehave.core.annotations.Then;
-
-import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.Map;
-
-public class MsbSteps {
-
-    private final static String DEFAULT_PACKAGE = "io.github.tcdl.msb.examples.";
-
-    protected final MsbTestHelper helper = MsbTestHelper.getInstance();
-    private final Map microserviceMap = new HashMap<>();
-
-    @Given("microservice $microservice")
-    public synchronized void startMicroservice(String microservice) throws Throwable {
-        startMicroserviceInternal(microservice, helper.getDefaultContext());
-    }
-
-    @Given("start microservice $microservice with context $contextName")
-    public synchronized void startMicroserviceWithContext(String microservice, String contextName) throws Throwable {
-        startMicroserviceInternal(microservice, helper.getContext(contextName));
-    }
-
-    private void startMicroserviceInternal(String microservice, MsbContext context) throws Exception {
-        Class microserviceClass = getClass().getClassLoader().loadClass(resolveClass(microservice));
-        Method startMethod = microserviceClass.getMethod("start", MsbContext.class);
-        Object microserviceInstance = microserviceClass.newInstance();
-        startMethod.invoke(microserviceInstance, context);
-        microserviceMap.putIfAbsent(microservice, microserviceInstance);
-    }
-
-    @Then("stop microservice $microservice")
-    public synchronized void stopMicroservice(String microservice) throws Throwable {
-        if (microserviceMap.containsKey(microservice)) {
-            Object microserviceInstance = microserviceMap.get(microservice);
-            Class microserviceClass = getClass().getClassLoader().loadClass(resolveClass(microservice));
-            Method stopMethod = microserviceClass.getMethod("stop");
-            stopMethod.invoke(microserviceInstance);
-            microserviceMap.remove(microservice);
-        }
-    }
-
-    protected String resolveClass(String microservice) {
-        if (microservice.startsWith("com.") || microservice.startsWith("io.")) {
-            return microservice;
-        } else {
-            return DEFAULT_PACKAGE + microservice;
-        }
-    }
-}
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 563de7e0..944654e2 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -4,6 +4,7 @@
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigValueFactory;
+import io.github.tcdl.msb.acceptance.MsbTestHelper;
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.Topics;
@@ -31,7 +32,9 @@
 /**
  * Steps to send requests and respond with predefined responses
  */
-public class RequesterResponderSteps extends MsbSteps {
+public class RequesterResponderSteps {
+
+    protected final MsbTestHelper helper = MsbTestHelper.getInstance();
 
     private volatile Requester requester;
     private volatile String responseBody;
@@ -66,35 +69,6 @@ public void createResponderServer(String namespace) {
         createResponderServer(DEFAULT_CONTEXT_NAME, namespace);
     }
 
-    @Given("responder server responds sequentially on namespace $namespace")
-    public void respondSequentially(String namespace) throws Exception {
-        ObjectMapper mapper = helper.getPayloadMapper(DEFAULT_CONTEXT_NAME);
-        helper.createResponderServer(DEFAULT_CONTEXT_NAME, namespace, (request, responderContext) -> {
-            if (responses.isEmpty()) {
-                return;
-            }
-
-            responses.forEach(nextResponse -> nextResponse.entrySet().stream().findFirst().ifPresent(entry -> {
-                switch (entry.getKey()) {
-                    case ACK:
-                        Integer responsesRemaining = (Integer) (entry.getValue());
-                        responderContext.getResponder().sendAck(ACK_TIMEOUT, responsesRemaining);
-                        try {
-                            TimeUnit.MILLISECONDS.sleep(ACK_TIMEOUT);
-                        } catch (InterruptedException e) {
-                            throw new RuntimeException(e);
-                        }
-                        break;
-                    case PAYLOAD:
-                        RestPayload payload = new RestPayload.Builder()
-                                .withBody(Utils.fromJson(responseBody, Map.class, mapper))
-                                .build();
-                        responderContext.getResponder().send(payload);
-                        break;
-                }
-            }));
-        }).listen();
-    }
 
     @Given("responder server from $contextName listens on namespace $namespace")
     @When("responder server from $contextName listens on namespace $namespace")
@@ -236,13 +210,6 @@ public void sendRequestWithTag(String tag) throws Exception {
         helper.sendRequest(requester, payload, responsesToExpectCount, this::onResponse, this::onEnd, tag);
     }
 
-    @When("requester sends a request with query '$query'")
-    public void sendRequestWithQuery(String query) throws Exception {
-        onBeforeRequest();
-        RestPayload payload = helper.createFacetParserPayload(query, null);
-        helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse, this::onEnd);
-    }
-
     @When("^requester sends a request with body '$body'$")
     public void sendRequestWithBody(String body) throws Exception {
         onBeforeRequest();
@@ -411,15 +378,6 @@ public void responseContains(ExamplesTable table) throws Exception {
         outcomes.verify();
     }
 
-    @When("requester sends a request for single result to namespace $namespace")
-    public void requestForSingleResult(String namespace) throws Exception {
-        String query = "QUERY";
-        String body = "body";
-        RestPayload payload = helper.createFacetParserPayload(query, body);
-        requester = helper.createRequester(namespace, 1, RestPayload.class);
-        lastFutureResult = helper.sendForResult(requester, payload);
-    }
-
     @When("requester sends to $namespace a request with body '$body' and routing key $routingKey")
     public void requestForSingleResult(String namespace, String body, String routingKey) throws Exception {
         helper.initDefault();
@@ -451,52 +409,6 @@ public void publishWithoutRoutingKey(String namespace, String forwardNamespace,
         publishWithRoutingKey(namespace, forwardNamespace, body, StringUtils.EMPTY);
     }
 
-    @When("requester blocks waiting for response for $timeout ms")
-    public void blockUntilResponseReceived(int timeout) throws Exception {
-        resolvedResponse = lastFutureResult.get(timeout, TimeUnit.MILLISECONDS);
-    }
-
-    @Then("resolved response equals $table")
-    public void verifyFutureResult(ExamplesTable table) {
-        Map expected = table.getRow(0);
-        OutcomesTable outcomes = new OutcomesTable();
-
-        for (String key : expected.keySet()) {
-            outcomes.addOutcome(key, resolvedResponse.getBody().toString(), Matchers.containsString(expected.get(key)));
-        }
-
-        outcomes.verify();
-    }
-
-    @Given("next response from responder contains acknowledge with $remaining remaining response")
-    public void addAckToResponsesQueue(int remainingResponses) {
-        Map request = new HashMap<>();
-        request.put(ACK, remainingResponses);
-        responses.add(request);
-    }
-
-    @Given("next response from responder contains body $responseBody")
-    public void addBodyToResponsesQueue(String responseBody) {
-        Map request = new HashMap<>();
-        request.put(PAYLOAD, responseBody);
-        responses.add(request);
-    }
-
-    @Then("requester gets exception when tries to obtain result")
-    public void assertExceptionOccured() throws Exception {
-        try {
-            resolvedResponse = lastFutureResult.get(5000, TimeUnit.MILLISECONDS);
-        } catch (CancellationException e) {
-            return;//ok
-        }
-        fail("Expected exception not thrown");
-    }
-
-//    @Then("reset mock responses")
-//    public void resetMockResponses() {
-//        responses.clear();
-//    }
-
     @BeforeScenario
     public void resetMockResponses() {
         responses.clear();
diff --git a/acceptance/src/test/resources/scenarios/blocking_requester.story b/acceptance/src/test/resources/scenarios/blocking_requester.story
deleted file mode 100644
index d8c41843..00000000
--- a/acceptance/src/test/resources/scenarios/blocking_requester.story
+++ /dev/null
@@ -1,37 +0,0 @@
-Lifecycle:
-Before:
-Given MSB configuration with consumer thread pool size 1
-And start MSB
-And clear log
-After:
-Outcome: ANY
-Then shutdown MSB
-
-Scenario: Requester sends request for single future response
-
-Given responder server responds with '{"result": "hello jbehave - future"}'
-And responder server listens on namespace test:jbehave
-When requester sends a request for single result to namespace test:jbehave
-And requester blocks waiting for response for 5000 ms
-Then resolved response equals
-|result|
-|hello jbehave - future|
-
-Scenario: Actual response comes after acknowledge
-
-Given next response from responder contains acknowledge with 1 remaining response
-And next response from responder contains body '{"result": "hello jbehave - future"}'
-And responder server responds sequentially on namespace test:jbehave
-When requester sends a request for single result to namespace test:jbehave
-And requester blocks waiting for response for 5000 ms
-Then resolved response equals
-|result|
-|hello jbehave - future|
-
-Scenario: To many responses
-
-Given next response from responder contains acknowledge with 2 remaining response
-And next response from responder contains body '{"result": "hello jbehave - future"}'
-And responder server responds sequentially on namespace test:jbehave
-When requester sends a request for single result to namespace test:jbehave
-Then requester gets exception when tries to obtain result
\ No newline at end of file
diff --git a/acceptance/src/test/resources/scenarios/date_extractor.story b/acceptance/src/test/resources/scenarios/date_extractor.story
deleted file mode 100644
index d7620c84..00000000
--- a/acceptance/src/test/resources/scenarios/date_extractor.story
+++ /dev/null
@@ -1,22 +0,0 @@
-Lifecycle:
-Before:
-Given start MSB
-And microservice DateExtractor
-After:
-Outcome: ANY
-Then shutdown MSB
-
-Scenario: Parsing date with date extractor microservice
-
-Given requester sends requests to namespace search:parsers:facets:v1
-When requester sends a request with query 'Holidays in 2015'
-Then requester gets response in 5000 ms
-And response contains
-|results|
-|year=15|
-
-When requester sends a request with query '2015-counter-2078'
-Then requester gets response in 5000 ms
-And response contains
-|results|
-|year=15|
diff --git a/acceptance/src/test/resources/scenarios/multiple_requests_to_date_extractor.story b/acceptance/src/test/resources/scenarios/multiple_requests_to_date_extractor.story
deleted file mode 100644
index 4bdc2926..00000000
--- a/acceptance/src/test/resources/scenarios/multiple_requests_to_date_extractor.story
+++ /dev/null
@@ -1,15 +0,0 @@
-Lifecycle:
-Before:
-Given MSB configuration with consumer thread pool size 5
-And MSB configuration with consumer thread pool queue capacity 20
-And MSB configuration with timer thread pool size 10
-And start MSB
-And microservice DateExtractor
-After:
-Outcome: ANY
-Then shutdown MSB
-
-Scenario: Sending multiple requests to date extractor microservice in parallel
-
-Given 2 requesters send a request to namespace search:parsers:facets:v1 with query 'Holidays in 2015'
-Then wait responses in 5000 ms
\ No newline at end of file
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
index 1ee19e30..8654985d 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
@@ -376,6 +376,9 @@ private Integer setResponsesRemainingForResponderId(String responderId, int resp
     }
 
     public void waitForResponses() {
+        if(this.responseTimeoutFuture != null){
+            this.responseTimeoutFuture.cancel(true);
+        }
         int newTimeoutMs = this.currentTimeoutMs - toIntExact(clock.instant().toEpochMilli() - this.startedAt);
         LOG.debug("[correlation id: {}] Waiting for responses until {}.", requestMessage.getCorrelationId(), clock.instant().plus(newTimeoutMs, ChronoUnit.MILLIS));
         this.responseTimeoutFuture = timeoutManager.enableResponseTimeout(newTimeoutMs, this);
diff --git a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java
index 0f1e1f9b..f28a0f50 100644
--- a/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/collector/CollectorTest.java
@@ -1074,6 +1074,31 @@ public void testProcessAckWillTakeDefaultTimeoutIsMaxAndNotCallTimerAgain() {
         verify(timeoutManagerMock, never()).enableResponseTimeout(anyInt(), any());
     }
 
+    @Test
+    public void testProcessAckExtendsTimeout() throws Exception {
+        int initialTimeout = 50;
+        int timeoutFromAck = 500;
+
+        ScheduledFuture timeoutTask1 = mock(ScheduledFuture.class);
+        ScheduledFuture timeoutTask2= mock(ScheduledFuture.class);
+
+        when(requestOptionsMock.getResponseTimeout()).thenReturn(initialTimeout);
+        Collector collector = createCollector();
+
+        doReturn(timeoutTask1)
+                .doReturn(timeoutTask2)
+                .when(timeoutManagerMock).enableResponseTimeout(anyInt(), same(collector));
+
+        collector.waitForResponses();
+        collector.processAck(new Acknowledge.Builder().withResponderId("irrelevant").withTimeoutMs(timeoutFromAck).build());
+
+        ArgumentCaptor intCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(timeoutTask1).cancel(anyBoolean());
+        verify(timeoutTask2, never()).cancel(anyBoolean());
+        verify(timeoutManagerMock, times(2)).enableResponseTimeout(intCaptor.capture(), same(collector));
+    }
+
     @Test
     public void testEndHandlerTimersStopped() {
         ScheduledFuture ackTimerMock = mock(ScheduledFuture.class);
diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java b/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java
deleted file mode 100644
index 417c2d53..00000000
--- a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractor.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package io.github.tcdl.msb.examples;
-
-import io.github.tcdl.msb.api.*;
-import io.github.tcdl.msb.api.message.payload.RestPayload;
-import io.github.tcdl.msb.examples.payload.Query;
-import io.github.tcdl.msb.examples.payload.Request;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Simple example of date parser micro-service
- * It listens requests from facets-aggregator and parses year from query string.
- */
-public class DateExtractor {
-
-    public static void main(String... args) {
-        MsbContext msbContext = new MsbContextBuilder()
-                .enableShutdownHook(true)
-                .build();
-        new DateExtractor().start(msbContext);
-    }
-
-    public void start(MsbContext msbContext) {
-        MessageTemplate messageTemplate = new MessageTemplate().withTags("date-extractor");
-        final String namespace = "search:parsers:facets:v1";
-
-        ResponderOptions responderOptions = new ResponderOptions.Builder().withMessageTemplate(messageTemplate).build();
-
-        msbContext.getObjectFactory().createResponderServer(namespace, responderOptions, (request, responderContext) -> {
-
-            Query query = request.getQuery();
-            String queryString = query.getQ();
-            String year = DateExtractorUtils.retrieveYear(queryString);
-
-            if (year != null) {
-                Responder responder = responderContext.getResponder();
-                // send acknowledge
-                responder.sendAck(500, null);
-
-                // populate response body
-                Result result = new Result();
-                result.setStr(year);
-                result.setStartIndex(queryString.indexOf(year));
-                result.setEndIndex(queryString.indexOf(year) + year.length() - 1);
-                result.setInferredDate(new HashMap<>());
-                result.setProbability(0.9f);
-
-                Result.Date date = new Result.Date();
-                date.setYear(Integer.parseInt(year.substring(2, year.length())));
-                result.setDate(date);
-
-                ResponseBody responseBody = new ResponseBody();
-                responseBody.setResults(Arrays.asList(result));
-                RestPayload responsePayload = new RestPayload.Builder()
-                        .withStatusCode(200)
-                        .withBody(responseBody)
-                        .build();
-
-                responder.send(responsePayload);
-            }
-        }, Request.class).listen();
-    }
-
-    private static class RequestQuery {
-
-        private String q;
-
-        public String getQ() {
-            return q;
-        }
-
-        public void setQ(String q) {
-            this.q = q;
-        }
-    }
-
-    private static class ResponseBody {
-        private List results;
-
-        public List getResults() {
-            return results;
-        }
-
-        public void setResults(List results) {
-            this.results = results;
-        }
-    }
-
-    private static class Result {
-        private String str;
-        private int startIndex;
-        private int endIndex;
-        private Date date;
-        private Map inferredDate;
-        private float probability;
-
-        public String getStr() {
-            return str;
-        }
-
-        public void setStr(String str) {
-            this.str = str;
-        }
-
-        public int getStartIndex() {
-            return startIndex;
-        }
-
-        public void setStartIndex(int startIndex) {
-            this.startIndex = startIndex;
-        }
-
-        public int getEndIndex() {
-            return endIndex;
-        }
-
-        public void setEndIndex(int endIndex) {
-            this.endIndex = endIndex;
-        }
-
-        public Date getDate() {
-            return date;
-        }
-
-        public void setDate(Date date) {
-            this.date = date;
-        }
-
-        public Map getInferredDate() {
-            return inferredDate;
-        }
-
-        public void setInferredDate(Map inferredDate) {
-            this.inferredDate = inferredDate;
-        }
-
-        public float getProbability() {
-            return probability;
-        }
-
-        public void setProbability(float probability) {
-            this.probability = probability;
-        }
-
-        private static class Date {
-            private int year;
-
-            public int getYear() {
-                return year;
-            }
-
-            public void setYear(int year) {
-                this.year = year;
-            }
-        }
-    }
-}
diff --git a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractorUtils.java b/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractorUtils.java
deleted file mode 100644
index 4615e209..00000000
--- a/examples/src/main/java/io/github/tcdl/msb/examples/DateExtractorUtils.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package io.github.tcdl.msb.examples;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class DateExtractorUtils {
-    private static final Pattern DATE_PATTERN =
-            Pattern.compile(".*?(((0?[1-9]|[12][0-9]|3[01])(/|\\.))?((0?[1-9]|1[012])(/|\\.))?((19|20)\\d\\d)).*");
-
-    public static String retrieveYear(String s) {
-        Matcher dateMatcher = DATE_PATTERN.matcher(s);
-        if (dateMatcher.matches()) {
-            String str = dateMatcher.group(1);
-            if (str.contains(".")) {
-                return str.split("\\.")[str.split("\\.").length - 1];
-            } else if (str.contains("/")) {
-                return str.split("/")[str.split("/").length - 1];
-            } else {
-                return str;
-            }
-        }
-        return null;
-    }
-}
diff --git a/examples/src/test/java/io/github/tcdl/msb/examples/DateExtractorUtilsTest.java b/examples/src/test/java/io/github/tcdl/msb/examples/DateExtractorUtilsTest.java
deleted file mode 100644
index d25511ad..00000000
--- a/examples/src/test/java/io/github/tcdl/msb/examples/DateExtractorUtilsTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package io.github.tcdl.msb.examples;
-
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-public class DateExtractorUtilsTest {
-
-    @Test
-    public void testOnlyYear() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("2015"));
-    }
-
-    @Test
-    public void testOnlyYearSpaces() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear(" 2015 "));
-    }
-
-    @Test
-    public void testMonthYear() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("02/2015"));
-    }
-
-    @Test
-    public void testMonthYearDot() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("02.2015"));
-    }
-
-    @Test
-    public void testDayMonthYear() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("12/03/2015"));
-    }
-
-    @Test
-    public void testDayMonthYearDot() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("12.03.2015"));
-    }
-
-    @Test
-    public void testDateTextStart() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("12/03/2015 London holidays"));
-    }
-
-    @Test
-    public void testDateTextStartEnd() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("Hotels 12/03/2015 London holidays"));
-    }
-
-    @Test
-    public void testDateTextStartEndDot() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("Hotels 12.03.2015 London holidays"));
-    }
-
-    @Test
-    public void testDateSpaceAtTheBeginning() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear(" 12/03/2015 London holidays"));
-    }
-
-    @Test
-    public void testDateSpaceAtEnd() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("12/03/2015 London holidays "));
-    }
-
-    @Test
-    public void testDateSpaceAtTheBeginningEnd() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear(" 12/03/2015 London holidays "));
-    }
-
-    @Test
-    public void testDateYear() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("London 03.03.2015-counter-2078"));
-    }
-
-    @Test
-    public void testTwoYears() {
-        assertEquals("2015", DateExtractorUtils.retrieveYear("London 2015-counter-2078"));
-    }
-}
\ No newline at end of file

From 46bdbf8a1cc954eef86c9ca7c37d5341dc65e4bc Mon Sep 17 00:00:00 2001
From: alex 
Date: Wed, 30 Nov 2016 13:55:49 +0200
Subject: [PATCH 174/226] Removed routing_keys.story and related steps. Test
 for routing keys support is rewritten to usual JUnit.

---
 .../tcdl/msb/acceptance/RoutingKeysTest.java  | 175 ++++++++++++++++++
 .../bdd/steps/RequesterResponderSteps.java    |  62 -------
 .../resources/scenarios/routing_keys.story    |  15 --
 3 files changed, 175 insertions(+), 77 deletions(-)
 create mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/RoutingKeysTest.java
 delete mode 100644 acceptance/src/test/resources/scenarios/routing_keys.story

diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RoutingKeysTest.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RoutingKeysTest.java
new file mode 100644
index 00000000..8bef51a9
--- /dev/null
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RoutingKeysTest.java
@@ -0,0 +1,175 @@
+package io.github.tcdl.msb.acceptance;
+
+import com.google.common.collect.Maps;
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigValueFactory;
+import io.github.tcdl.msb.api.*;
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+
+public class RoutingKeysTest {
+
+    private static final String NAMESPACE = "test:routing";
+
+    final List contexts = new ArrayList<>();
+
+    @After
+    public void tearDown() throws Exception {
+        contexts.forEach(MsbContext::shutdown);
+        contexts.clear();
+    }
+
+    @Test
+    public void consumerReceivesMessagesFilteredByRoutingKeys_ifRoutingKeysAreSupported() throws Exception {
+
+        Config baseConfig = ConfigFactory.load();
+
+        MsbContext responder1Context = distinctContext(baseConfig);
+        MsbContext responder2Context = distinctContext(baseConfig);
+
+        MsbContext requesterContext = distinctContext(baseConfig);
+
+        String routingKey1 = "routing-key-1";
+        String routingKey2 = "routing-key-2";
+        String routingKey3 = "routing-key-3";
+
+        String message1 = "message1";
+        String message2 = "message2";
+        String message3 = "message3";
+
+        CompletableFuture deferredResult1 = new CompletableFuture<>();
+        CompletableFuture deferredResult2 = new CompletableFuture<>();
+        CompletableFuture deferredResult3 = new CompletableFuture<>();
+
+        ConcurrentMap> deferredResults1 = Maps.newConcurrentMap();
+        deferredResults1.put(message1, deferredResult1);
+        deferredResults1.put(message2, deferredResult2);
+        setUpResponderForRoutingKeys(responder1Context, newHashSet(routingKey1, routingKey2), arrivingMessagesChecker(deferredResults1));
+
+        ConcurrentMap> deferredResults2 = Maps.newConcurrentMap();
+        deferredResults2.put(message3, deferredResult3);
+        setUpResponderForRoutingKeys(responder2Context, newHashSet(routingKey3), arrivingMessagesChecker(deferredResults2));
+
+        //publish messages with different routing keys
+        publishMessage(requesterContext, ExchangeType.TOPIC, routingKey1, message1);
+        publishMessage(requesterContext, ExchangeType.TOPIC, routingKey2, message2);
+        publishMessage(requesterContext, ExchangeType.TOPIC, routingKey3, message3);
+
+        //wait for at most 1 second and check
+        CompletableFuture combinedDeferredResult = CompletableFuture.allOf(deferredResult1, deferredResult2, deferredResult3);
+        combinedDeferredResult.get(1, TimeUnit.SECONDS);
+        assertFalse(combinedDeferredResult.isCancelled());
+    }
+
+    @Test
+    public void consumerReceivesAllMessages_ifRoutingKeysAreNotSupported() throws Exception {
+        Config baseConfig = ConfigFactory.load();
+
+        MsbContext responder1Context = distinctContext(baseConfig);
+        MsbContext responder2Context = distinctContext(baseConfig);
+
+        MsbContext requesterContext = distinctContext(baseConfig);
+
+        String routingKey1 = "routing-key-1";
+        String routingKey2 = "routing-key-2";
+
+        String message1 = "message1";
+        String message2 = "message2";
+
+        CountDownLatch expectedMessagesCountDown = new CountDownLatch(4); //2 for each consumer
+
+        //set up fanout exchange with two queues bound using routing keys
+        responder1Context.getObjectFactory().createResponderServer(NAMESPACE, new ResponderOptions.Builder().withBindingKeys(newHashSet(routingKey1)).build(),
+                (request, responderContext) -> expectedMessagesCountDown.countDown(),
+                String.class)
+                .listen();
+
+        responder2Context.getObjectFactory().createResponderServer(NAMESPACE, new ResponderOptions.Builder().withBindingKeys(newHashSet(routingKey1)).build(),
+                (request, responderContext) -> expectedMessagesCountDown.countDown(),
+                String.class)
+                .listen();
+
+        //publish messages with different routing keys
+        publishMessage(requesterContext, ExchangeType.FANOUT, routingKey1, message1);
+        publishMessage(requesterContext, ExchangeType.FANOUT, routingKey2, message2);
+
+        //check that all messages where received by all consumers
+        assertTrue("", expectedMessagesCountDown.await(1, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void consumerByDefaultReceivesMessages() throws Exception {
+        Config baseConfig = ConfigFactory.load();
+        MsbContext requesterContext = distinctContext(baseConfig);
+        MsbContext responderContext = distinctContext(baseConfig);
+
+        CountDownLatch expectedMessagesCountDown = new CountDownLatch(1);
+
+        //routing key not specified
+        ResponderOptions responderOptions = new AmqpResponderOptions.Builder().withExchangeType(ExchangeType.TOPIC).build();
+
+        responderContext.getObjectFactory().createResponderServer(NAMESPACE, responderOptions,
+                (request, responderContext1) -> expectedMessagesCountDown.countDown(), String.class)
+                .listen();
+
+        RequestOptions requestOptions = new AmqpRequestOptions.Builder().withExchangeType(ExchangeType.TOPIC).withRoutingKey("some.routing.key").build();
+        requesterContext.getObjectFactory().createRequesterForFireAndForget(NAMESPACE, requestOptions).publish("message");
+
+        assertTrue(expectedMessagesCountDown.await(1, TimeUnit.SECONDS));
+    }
+
+    private Consumer arrivingMessagesChecker(ConcurrentMap> deferredResults) {
+        return (message) -> {
+            if (deferredResults.containsKey(message)) {
+                deferredResults.get(message).complete(null); //value is irrelevant
+            } else {
+                deferredResults.values().forEach(result -> result.cancel(false));
+            }
+        };
+    }
+
+    private MsbContext distinctContext(Config baseConfig) {
+        Config config = baseConfig
+                .withValue("msbConfig.serviceDetails.name", ConfigValueFactory.fromAnyRef(UUID.randomUUID().toString()))
+                .withValue("msbConfig.brokerConfig.durable", ConfigValueFactory.fromAnyRef(false));
+
+        MsbContext context = new MsbContextBuilder().withConfig(config).build();
+        contexts.add(context);
+        return context;
+    }
+
+    private void publishMessage(MsbContext requesterContext, ExchangeType exchangeType, String routingKey, String message) {
+        RequestOptions requestOptions = new AmqpRequestOptions.Builder()
+                .withExchangeType(exchangeType)
+                .withRoutingKey(routingKey)
+                .build();
+        requesterContext.getObjectFactory().createRequester(NAMESPACE, requestOptions, String.class).publish(message);
+    }
+
+    private void setUpResponderForRoutingKeys(MsbContext context, Set bindingKeys, Consumer messageHandler) {
+        ResponderOptions responderOptions = new AmqpResponderOptions.Builder()
+                .withExchangeType(ExchangeType.TOPIC)
+                .withBindingKeys(bindingKeys).build();
+
+        context.getObjectFactory().createResponderServer(NAMESPACE, responderOptions,
+                (request, responderContext) -> messageHandler.accept(request),
+                String.class)
+                .listen();
+    }
+}
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 944654e2..06464d12 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -217,26 +217,6 @@ public void sendRequestWithBody(String body) throws Exception {
         helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse, this::onEnd);
     }
 
-    @Given("$responderId responder server listens on namespace $namespace with binding keys $routingKeys")
-    public void subscribeResponder(String responderId, String namespace, List routingKeys) {
-        //modify name to make library generate different queue names for different consumers (responders)
-        Config config = ConfigFactory.load()
-                .withValue("msbConfig.serviceDetails.name", ConfigValueFactory.fromAnyRef("msb_java_" + responderId));
-
-        helper.initWithConfig(responderId, config);
-        MsbContext context = helper.getContext(responderId);
-
-        ResponderOptions amqpResponderOptions = new AmqpResponderOptions.Builder()
-                .withBindingKeys(new HashSet<>(routingKeys))
-                .withExchangeType(ExchangeType.TOPIC)
-                .build();
-
-        context.getObjectFactory().createResponderServer(namespace, amqpResponderOptions,
-                (request, responderContext) -> {
-                    receivedMessagesByConsumer.computeIfAbsent(responderId, key -> new LinkedList<>()).add(request);
-                }, String.class).listen();
-    }
-
     @Given("responder server listens on fanout namespace $namespace")
     public void subscribeResponder(String namespace) {
         MsbContext context = helper.getDefaultContext();
@@ -246,17 +226,6 @@ public void subscribeResponder(String namespace) {
                 }, String.class).listen();
     }
 
-    @Then("$responderId responder receives only messages $messages")
-    public void assertReceivedMessages(String responderId, List expectedMessagesRaw) throws InterruptedException {
-
-        List expectedMessages = expectedMessagesRaw.stream()
-                .map(message -> message.substring(1, message.length() - 1)) //remove surrounding ' symbols
-                .collect(Collectors.toList());
-        TimeUnit.SECONDS.sleep(1); //wait until messages will be delivered
-        List capturedMessages = receivedMessagesByConsumer.get(responderId);
-        assertTrue(CollectionUtils.isEqualCollection(expectedMessages, capturedMessages));
-    }
-
     private void onBeforeRequest() {
         receivedResponse = null;
         receivedResponseFuture = new CompletableFuture<>();
@@ -378,37 +347,6 @@ public void responseContains(ExamplesTable table) throws Exception {
         outcomes.verify();
     }
 
-    @When("requester sends to $namespace a request with body '$body' and routing key $routingKey")
-    public void requestForSingleResult(String namespace, String body, String routingKey) throws Exception {
-        helper.initDefault();
-        RequestOptions requestOptions = new AmqpRequestOptions.Builder()
-                .withExchangeType(ExchangeType.TOPIC)
-                .withRoutingKey(routingKey).build();
-
-        helper.getContext(DEFAULT_CONTEXT_NAME).getObjectFactory()
-                .createRequesterForFireAndForget(namespace, requestOptions)
-                .publish(body);
-    }
-
-    @When("requester sends to $namespace a request with forward namespace $forwardNamespace, body '$body' and routing key $routingKey")
-    public void publishWithRoutingKey(String namespace, String forwardNamespace, String body, String routingKey) throws Exception {
-        helper.initDefault();
-
-        RequestOptions requestOptions = new RequestOptions.Builder()
-                .withForwardNamespace(forwardNamespace)
-                .withRoutingKey(routingKey)
-                .build();
-
-        helper.getContext(DEFAULT_CONTEXT_NAME).getObjectFactory()
-                .createRequesterForFireAndForget(namespace, requestOptions)
-                .publish(body);
-    }
-
-    @When("requester sends to $namespace a request with forward namespace $forwardNamespace, body '$body' without routing key")
-    public void publishWithoutRoutingKey(String namespace, String forwardNamespace, String body) throws Exception {
-        publishWithRoutingKey(namespace, forwardNamespace, body, StringUtils.EMPTY);
-    }
-
     @BeforeScenario
     public void resetMockResponses() {
         responses.clear();
diff --git a/acceptance/src/test/resources/scenarios/routing_keys.story b/acceptance/src/test/resources/scenarios/routing_keys.story
deleted file mode 100644
index a24f1639..00000000
--- a/acceptance/src/test/resources/scenarios/routing_keys.story
+++ /dev/null
@@ -1,15 +0,0 @@
-Lifecycle:
-Before:
-Given start MSB
-After:
-Outcome: ANY
-Then shutdown MSB
-
-Scenario: consumers receive messages according to routing keys
-Given 1st responder server listens on namespace test:namespace with binding keys routing-key-1, routing-key-2
-Given 2nd responder server listens on namespace test:namespace with binding keys routing-key-3
-When requester sends to test:namespace a request with body '{"messageId":"rk1"}' and routing key routing-key-1
-When requester sends to test:namespace a request with body '{"messageId":"rk2"}' and routing key routing-key-2
-When requester sends to test:namespace a request with body '{"messageId":"rk3"}' and routing key routing-key-3
-Then 1st responder receives only messages '{"messageId":"rk1"}', '{"messageId":"rk2"}'
-Then 2nd responder receives only messages '{"messageId":"rk3"}'
\ No newline at end of file

From a5c4da1d98718231600d5835482d9e4099eff312 Mon Sep 17 00:00:00 2001
From: alex 
Date: Wed, 30 Nov 2016 14:16:10 +0200
Subject: [PATCH 175/226] Removed channel_recreation.story because it didn't
 actually test recovery mechanism.

---
 .../bdd/steps/ConfigurationSteps.java         | 12 ------
 .../bdd/steps/RequesterResponderSteps.java    | 37 ++-----------------
 .../scenarios/channel_recreation.story        | 28 --------------
 3 files changed, 4 insertions(+), 73 deletions(-)
 delete mode 100644 acceptance/src/test/resources/scenarios/channel_recreation.story

diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
index 00792ae1..cfb7b64a 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
@@ -46,18 +46,6 @@ public void initMSB() {
         helper.initWithConfig(config);
     }
 
-    @Given("init MSB context $contextName")
-    @When("init MSB context $contextName")
-    public void initMsbContext(String contextName) {
-        helper.initWithConfig(contextName, config);
-    }
-
-    @Then("shutdown context $contextName")
-    @When("shutdown context $contextName")
-    public void shutdownMsbContext(String contextName) {
-        helper.shutdown(contextName);
-    }
-
     @Then("shutdown MSB")
     public void shutdownMSB() {
         helper.shutdown();
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
index 06464d12..bb5678b9 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
@@ -1,17 +1,12 @@
 package io.github.tcdl.msb.acceptance.bdd.steps;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.typesafe.config.Config;
-import com.typesafe.config.ConfigFactory;
-import com.typesafe.config.ConfigValueFactory;
 import io.github.tcdl.msb.acceptance.MsbTestHelper;
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.Topics;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
 import io.github.tcdl.msb.support.Utils;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
 import org.hamcrest.Matchers;
 import org.jbehave.core.annotations.BeforeScenario;
 import org.jbehave.core.annotations.Given;
@@ -24,7 +19,6 @@
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
 
 import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME;
 import static org.junit.Assert.*;
@@ -50,32 +44,15 @@ public class RequesterResponderSteps {
     private volatile int responsesToSendCount;
     private volatile int responsesToExpectCount;
     private volatile String latestForwardNamespace = null;
-    private CompletableFuture lastFutureResult = null;
-    private RestPayload resolvedResponse = null;
     private final LinkedList> responses = new LinkedList<>();
-    private final String ACK = "ACK";
-    private final String PAYLOAD = "PAYLOAD";
-    private final int ACK_TIMEOUT = 500;
-    private final Map> receivedMessagesByConsumer = new ConcurrentHashMap<>();
     private final Deque rawIncomingMessages = new ConcurrentLinkedDeque<>();
 
-    public Optional getDefaultRequestsAckType() {
-        return defaultRequestsAckType;
-    }
-
     // responder steps
     @Given("responder server listens on namespace $namespace")
     public void createResponderServer(String namespace) {
-        createResponderServer(DEFAULT_CONTEXT_NAME, namespace);
-    }
-
-
-    @Given("responder server from $contextName listens on namespace $namespace")
-    @When("responder server from $contextName listens on namespace $namespace")
-    public void createResponderServer(String contextName, String namespace) {
         beforeCreateResponder();
-        ObjectMapper mapper = helper.getPayloadMapper(contextName);
-        helper.createResponderServer(contextName, namespace, (request, responderContext) -> {
+        ObjectMapper mapper = helper.getPayloadMapper(DEFAULT_CONTEXT_NAME);
+        helper.createResponderServer(DEFAULT_CONTEXT_NAME, namespace, (request, responderContext) -> {
             if (responseBody == null) {
                 return;
             }
@@ -169,7 +146,7 @@ public void respond(String body) {
     // requester steps
     @Given("requester sends requests to namespace $namespace")
     public void createRequester(String namespace) {
-        createRequester(DEFAULT_CONTEXT_NAME, namespace);
+        requester = helper.createRequester(DEFAULT_CONTEXT_NAME, namespace, null, 1, RestPayload.class);
     }
 
     // requester steps
@@ -186,18 +163,12 @@ public void createRequester(int requestTimeout, int responseCount, String namesp
         requester = helper.createRequester(DEFAULT_CONTEXT_NAME, namespace, null, responsesToExpectCount, 100, requestTimeout, RestPayload.class);
     }
 
-    @Given("requester from $contextName sends requests to namespace $namespace")
-    public void createRequester(String contextName, String namespace) {
-        requester = helper.createRequester(contextName, namespace, null, 1, RestPayload.class);
-    }
-
     @When("requester sends a request")
     public void sendRequest() throws Exception {
         sendRequest(DEFAULT_CONTEXT_NAME);
     }
 
-    @When("requester from $contextName sends a request")
-    public void sendRequest(String contextName) throws Exception {
+    private void sendRequest(String contextName) throws Exception {
         onBeforeRequest();
         RestPayload payload = helper.createFacetParserPayload("QUERY", null);
         helper.sendRequest(requester, payload, responsesToExpectCount, this::onResponse, this::onEnd);
diff --git a/acceptance/src/test/resources/scenarios/channel_recreation.story b/acceptance/src/test/resources/scenarios/channel_recreation.story
deleted file mode 100644
index 1aebcd19..00000000
--- a/acceptance/src/test/resources/scenarios/channel_recreation.story
+++ /dev/null
@@ -1,28 +0,0 @@
-Lifecycle:
-Before:
-Given init MSB context contextResponder
-And init MSB context contextRequester
-And clear log
-After:
-Outcome: ANY
-Then shutdown context contextRequester
-And shutdown context contextResponder
-
-Scenario: Sends a request to a responder server and gets response
-Given responder server from contextResponder listens on namespace test:jbehave
-And responder server responds with '{"result": "hello jbehave"}'
-And requester from contextRequester sends requests to namespace test:jbehave
-When requester from contextRequester sends a request
-Then requester gets response in 5000 ms
-And response equals
-|result|
-|hello jbehave|
-
-When init MSB context contextResponder
-And responder server from contextResponder listens on namespace test:jbehave
-And responder server responds with '{"result": "hello jbehave"}'
-And requester from contextRequester sends a request
-Then requester gets response in 5000 ms
-And response equals
-|result|
-|hello jbehave|

From 2594d25dac549c480e7cf66d2301a08fdd1c5c09 Mon Sep 17 00:00:00 2001
From: alex 
Date: Wed, 30 Nov 2016 14:57:55 +0200
Subject: [PATCH 176/226] Removed RequesterResponderTest because it was not a
 test + necessary refactoring

---
 .../acceptance/RequesterResponderTest.java    | 45 -------------------
 .../acceptance/RequesterResponderRunner.java  | 43 ++++++++++++++++--
 2 files changed, 39 insertions(+), 49 deletions(-)
 delete mode 100644 acceptance/src/main/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java

diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java
deleted file mode 100644
index f2da486b..00000000
--- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package io.github.tcdl.msb.acceptance;
-
-import io.github.tcdl.msb.api.Requester;
-import io.github.tcdl.msb.api.Responder;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class RequesterResponderTest {
-
-    private static final Integer NUMBER_OF_RESPONSES = 1;
-
-    final String NAMESPACE = "test:requester-responder-example";
-
-    private MsbTestHelper helper = MsbTestHelper.getInstance();
-
-    private CountDownLatch passedLatch;
-
-    public boolean isPassed() {
-        try {
-            passedLatch.await(15, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            return false;
-        }
-
-        return passedLatch.getCount() == 0;
-    }
-
-    public void runRequesterResponder() throws Exception {
-        helper.initDefault();
-        // running responder server
-        helper.createResponderServer(NAMESPACE, (request, responderContext) -> {
-            System.out.println(">>> REQUEST: " + request);
-            Responder responder = responderContext.getResponder();
-            responder.sendAck(1000, NUMBER_OF_RESPONSES);
-            responder.send("Pong");
-        }, String.class)
-        .listen();
-
-        // sending a request
-        Requester requester = helper.createRequester(NAMESPACE, NUMBER_OF_RESPONSES, String.class);
-        passedLatch = new CountDownLatch(1);
-        helper.sendRequest(requester, "Ping", true, NUMBER_OF_RESPONSES, arg -> {}, payload -> passedLatch.countDown());
-    }
-}
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderRunner.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderRunner.java
index 119d157c..d3607f32 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderRunner.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderRunner.java
@@ -1,16 +1,51 @@
 package io.github.tcdl.msb.acceptance;
 
+import io.github.tcdl.msb.api.Requester;
+import io.github.tcdl.msb.api.Responder;
 import org.junit.Test;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 import static org.junit.Assert.assertTrue;
 
-public class RequesterResponderRunner {
+public class RequesterResponderRunner { //todo clean refactor or throw away if same functionality is covered by the other tests
+
+    private static final Integer NUMBER_OF_RESPONSES = 1;
+
+    final String NAMESPACE = "test:requester-responder-example";
+
+    private MsbTestHelper helper = MsbTestHelper.getInstance();
+
+    private CountDownLatch passedLatch;
 
     @Test
     public void runTest() throws Exception {
-        RequesterResponderTest test = new RequesterResponderTest();
-        test.runRequesterResponder();
+        helper.initDefault();
+        // running responder server
+        helper.createResponderServer(NAMESPACE, (request, responderContext) -> {
+            System.out.println(">>> REQUEST: " + request);
+            Responder responder = responderContext.getResponder();
+            responder.sendAck(1000, NUMBER_OF_RESPONSES);
+            responder.send("Pong");
+        }, String.class)
+        .listen();
+
+        // sending a request
+        Requester requester = helper.createRequester(NAMESPACE, NUMBER_OF_RESPONSES, String.class);
+        passedLatch = new CountDownLatch(1);
+        helper.sendRequest(requester, "Ping", true, NUMBER_OF_RESPONSES, arg -> {}, payload -> passedLatch.countDown());
+
+        assertTrue(isPassed());
+    }
+
+    public boolean isPassed() {
+        try {
+            passedLatch.await(15, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            return false;
+        }
 
-        assertTrue(test.isPassed());
+        return passedLatch.getCount() == 0;
     }
 }

From d45322d4dd7315ee2847d7c2bdd96a3280b7d16c Mon Sep 17 00:00:00 2001
From: alex 
Date: Fri, 2 Dec 2016 14:28:37 +0200
Subject: [PATCH 177/226] JBhave stories removed, tests rewritten

---
 acceptance/pom.xml                            |   5 -
 .../tcdl/msb/acceptance/MsbTestHelper.java    |  71 ++--
 .../acceptance/RequesterResponderTest.java    | 255 ++++++++++++++
 .../tcdl/msb/acceptance/RoutingKeysTest.java  |  16 +-
 .../msb/acceptance/bdd/ScenarioRunner.java    |  53 ---
 .../bdd/steps/ConfigurationSteps.java         |  55 ---
 .../msb/acceptance/bdd/steps/LoggerSteps.java |  20 --
 .../bdd/steps/RequesterResponderSteps.java    | 325 ------------------
 .../scenarios/requester_responder.story       | 134 --------
 pom.xml                                       |   5 -
 10 files changed, 291 insertions(+), 648 deletions(-)
 create mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java
 delete mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/ScenarioRunner.java
 delete mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
 delete mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java
 delete mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
 delete mode 100644 acceptance/src/test/resources/scenarios/requester_responder.story

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 2ae38817..b61d039f 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -30,11 +30,6 @@
             io.github.tcdl.msb
             msb-java-amqp
         
-        
-            org.jbehave
-            jbehave-core
-            test
-        
         
             io.github.tcdl.msb
             msb-java-examples
diff --git a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
index 2fd9258d..104ac039 100644
--- a/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
+++ b/acceptance/src/main/java/io/github/tcdl/msb/acceptance/MsbTestHelper.java
@@ -2,6 +2,7 @@
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.typesafe.config.Config;
+import com.typesafe.config.ConfigValueFactory;
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
@@ -10,7 +11,7 @@
 
 import java.util.HashMap;
 import java.util.Map;
-import java.util.concurrent.CompletableFuture;
+import java.util.UUID;
 
 /**
  * Utility to simplify using requester and responder server
@@ -33,19 +34,35 @@ public static MsbTestHelper getInstance() {
         return instance;
     }
 
-    public void initDefault() {
-        contextMap.put(DEFAULT_CONTEXT_NAME, new MsbContextBuilder()
-                .enableShutdownHook(true).build());
+    public MsbContext initDefault() {
+        return contextMap.put(DEFAULT_CONTEXT_NAME, new MsbContextBuilder()
+                .build());
     }
 
-    public void initWithConfig(Config config) {
-        initWithConfig(DEFAULT_CONTEXT_NAME, config);
+    public MsbContext initWithConfig(Config config) {
+        return initWithConfig(DEFAULT_CONTEXT_NAME, config);
     }
 
-    public void initWithConfig(String contextName, Config config) {
-        contextMap.put(contextName, new MsbContextBuilder()
-                .withConfig(config)
-                .enableShutdownHook(true).build());
+    public MsbContext initWithConfig(String contextName, Config config) {
+        MsbContext context = new MsbContextBuilder().withConfig(config).build();
+        contextMap.put(contextName, context);
+        return context;
+    }
+
+    /**
+     * Sets exchanges to be non-durable and queues to be temporary. This reduces the chances for tests to affect each other.
+     */
+    public static Config temporaryInfrastructure(Config config){
+        return config.withValue("msbConfig.brokerConfig.durable", ConfigValueFactory.fromAnyRef(false));
+    }
+
+    /**
+     * Creates MsbContext that will create unique (from context to context) queue names.
+     */
+    public MsbContext initDistinctContext(Config baseConfig) {
+        String uuid = UUID.randomUUID().toString();
+        Config config = baseConfig.withValue("msbConfig.serviceDetails.name", ConfigValueFactory.fromAnyRef(uuid));
+        return initWithConfig(uuid, config);
     }
 
     public MsbContext getContext(String contextName) {
@@ -60,10 +77,6 @@ public ObjectMapper getPayloadMapper(String contextName) {
         return ((MsbContextImpl) getContext(contextName)).getPayloadMapper();
     }
 
-    public ObjectMapper getPayloadMapper() {
-        return getPayloadMapper(DEFAULT_CONTEXT_NAME);
-    }
-
     public  Requester createRequester(String namespace, Integer numberOfResponses, Class responsePayloadClass) {
         return createRequester(DEFAULT_CONTEXT_NAME, namespace, null, numberOfResponses, null, null, responsePayloadClass);
     }
@@ -90,14 +103,6 @@ public  void sendRequest(Requester requester, Object payload, Integer wait
         sendRequest(requester, payload, true, waitForResponses, null, responseCallback, null);
     }
 
-    public  void sendRequest(Requester requester, Object payload, Integer waitForResponses, Callback responseCallback, Callback endCallback, String tag) throws Exception {
-        sendRequest(requester, payload, true, waitForResponses, null, responseCallback, endCallback, tag);
-    }
-
-    public  void sendRequest(Requester requester, Object payload, Integer waitForResponses, Callback responseCallback, Callback endCallback) throws Exception {
-        sendRequest(requester, payload, true, waitForResponses, null, responseCallback, endCallback);
-    }
-
     public  void sendRequest(Requester requester, Object payload, boolean waitForAck, Integer waitForResponses,
             Callback ackCallback, Callback responseCallback) throws Exception {
         sendRequest(requester, payload, waitForAck, waitForResponses,
@@ -135,10 +140,6 @@ public  void sendRequest(Requester requester, Object payload, boolean wait
         requester.publish(payload, tag);
     }
 
-    public  CompletableFuture sendForResult(Requester requester, Object payload, String... tags){
-        return requester.request(payload, tags);
-    }
-
     public ResponderServer createResponderServer(String contextName, String namespace, ResponderServer.RequestHandler requestHandler) {
         System.out.println(">>> RESPONDER SERVER on: " + namespace);
         return getContext(contextName).getObjectFactory().createResponderServer(namespace, ResponderOptions.DEFAULTS, requestHandler, RestPayload.class);
@@ -158,18 +159,14 @@ public void shutdown(String contextName) {
     }
 
     public void shutdown() {
-        getDefaultContext().shutdown();
+        MsbContext context = contextMap.remove(DEFAULT_CONTEXT_NAME);
+        if(context != null){
+            context.shutdown();
+        }
     }
 
-    public RestPayload createFacetParserPayload(String query, String body) {
-        Map queryMap = new HashMap<>();
-        queryMap.put("q", query);
-
-        Map bodyMap = new HashMap<>();
-        bodyMap.put("body", body);
-        return new RestPayload.Builder, Object, Object, Map>()
-                .withQuery(queryMap)
-                .withBody(bodyMap)
-                .build();
+    public void shutdownAll(){
+        contextMap.values().forEach(MsbContext::shutdown);
+        contextMap.clear();
     }
 }
\ No newline at end of file
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java
new file mode 100644
index 00000000..d05ce2d5
--- /dev/null
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java
@@ -0,0 +1,255 @@
+package io.github.tcdl.msb.acceptance;
+
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import io.github.tcdl.msb.api.MsbContext;
+import io.github.tcdl.msb.api.RequestOptions;
+import io.github.tcdl.msb.api.ResponderOptions;
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class RequesterResponderTest {
+
+    private MsbTestHelper helper = MsbTestHelper.getInstance();
+    Config config = ConfigFactory.load();
+
+    private static final String RESPONSE_BODY = "hello requester";
+    private static final String REQUEST_BODY = "hello responder";
+    private static final String NAMESPACE = "test:requester-responder";
+    private static final int TIMEOUT_MS = 5000;
+
+    @After
+    public void tearDown() throws Exception {
+        helper.shutdownAll();
+    }
+
+    @Test
+    public void simpleRequestResponse() throws Exception {
+
+        MsbContext responderContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+        responderContext.getObjectFactory().createResponderServer(NAMESPACE, ResponderOptions.DEFAULTS, (req, ctx) -> {
+            ctx.getResponder().send(RESPONSE_BODY);
+        }, String.class)
+                .listen();
+
+        MsbContext requesterContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+
+        int timeoutMs = 5000;
+        CountDownLatch latch = new CountDownLatch(1);
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(1)
+                .withResponseTimeout(timeoutMs)
+                .build();
+
+        requesterContext.getObjectFactory()
+                .createRequester(NAMESPACE, requestOptions, String.class)
+                .onResponse((resp, ctx) -> {
+                    if (RESPONSE_BODY.equals(resp)) {
+                        latch.countDown();
+                    }
+                })
+                .publish(REQUEST_BODY);
+
+        assertTrue("Expected response not received in time", latch.await(timeoutMs, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void tagsAreSentWithTheMessage() throws Exception {
+
+        CountDownLatch latch = new CountDownLatch(1);
+        String tag = "CUSTOM_TAG";
+
+        MsbContext responderContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+        responderContext.getObjectFactory().createResponderServer(NAMESPACE, ResponderOptions.DEFAULTS,
+                (req, ctx) -> {
+                    if (ctx.getOriginalMessage().getTags().contains(tag)) {
+                        latch.countDown();
+                    }
+                }, String.class)
+                .listen();
+
+        MsbContext requesterContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(1)
+                .withResponseTimeout(TIMEOUT_MS)
+                .build();
+
+        requesterContext.getObjectFactory()
+                .createRequesterForFireAndForget(NAMESPACE, requestOptions)
+                .publish(REQUEST_BODY, tag);
+
+        assertTrue("Message with the expected tag not received in time", latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void singleRedeliveryOnRetry() throws Exception {
+
+        MsbContext requesterContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+        MsbContext responderContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+
+        int expectedDeliveryCount = 2;
+
+        AtomicInteger deliveryCount = new AtomicInteger(0);
+        responderContext.getObjectFactory().createResponderServer(NAMESPACE, ResponderOptions.DEFAULTS,
+                (request, ctx) -> {
+                    deliveryCount.incrementAndGet();
+                    ctx.getAcknowledgementHandler().retryMessage();
+                },
+                String.class)
+                .listen();
+
+        requesterContext.getObjectFactory().createRequesterForFireAndForget(NAMESPACE).publish(REQUEST_BODY);
+
+        //wait a bit
+        TimeUnit.SECONDS.sleep(1);
+        assertEquals("Incorrect delivery count", expectedDeliveryCount, deliveryCount.get());
+    }
+
+    @Test
+    public void responseAfterRedelivery() throws Exception {
+
+        MsbContext requesterContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+        MsbContext responderContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+
+        AtomicInteger deliveryCount = new AtomicInteger(0);
+        responderContext.getObjectFactory().createResponderServer(NAMESPACE, ResponderOptions.DEFAULTS,
+                (request, ctx) -> {
+                    deliveryCount.incrementAndGet();
+                    if (deliveryCount.get() > 1) {
+                        ctx.getResponder().send(RESPONSE_BODY);
+                    } else {
+                        ctx.getAcknowledgementHandler().retryMessage();
+                    }
+
+                },
+                String.class)
+                .listen();
+
+        CompletableFuture deferredResponse = requesterContext.getObjectFactory()
+                .createRequesterForSingleResponse(NAMESPACE, String.class)
+                .request(REQUEST_BODY);
+
+        String response = deferredResponse.get(1, TimeUnit.SECONDS);
+        assertEquals("Unexpected response body", RESPONSE_BODY, response);
+    }
+
+    @Test
+    public void responseFromDifferentThread() throws Exception {
+
+        MsbContext requesterContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+        MsbContext responderContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+
+        responderContext.getObjectFactory().createResponderServer(NAMESPACE, ResponderOptions.DEFAULTS,
+                (request, ctx) -> new Thread(() -> ctx.getResponder().send(RESPONSE_BODY)).start(),
+                String.class)
+                .listen();
+
+        CompletableFuture deferredResponse = requesterContext.getObjectFactory()
+                .createRequesterForSingleResponse(NAMESPACE, String.class)
+                .request(REQUEST_BODY);
+
+        String response = deferredResponse.get(1, TimeUnit.SECONDS);
+        assertEquals("Unexpected response body", RESPONSE_BODY, response);
+    }
+
+    @Test
+    public void rejectedMessageNotRedelivered() throws Exception {
+
+        MsbContext requesterContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+        MsbContext responderContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+
+        int expectedDeliveryCount = 1;
+
+        AtomicInteger deliveryCount = new AtomicInteger(0);
+        responderContext.getObjectFactory().createResponderServer(NAMESPACE, ResponderOptions.DEFAULTS,
+                (request, ctx) -> {
+                    deliveryCount.incrementAndGet();
+                    ctx.getAcknowledgementHandler().rejectMessage();
+                },
+                String.class)
+                .listen();
+
+        requesterContext.getObjectFactory().createRequesterForFireAndForget(NAMESPACE).publish(REQUEST_BODY);
+
+        //wait a bit
+        TimeUnit.SECONDS.sleep(1);
+        assertEquals("Incorrect delivery count", expectedDeliveryCount, deliveryCount.get());
+    }
+
+    @Test
+    public void longRunningOnResponseHandler() throws Exception {
+
+        int responsesCount = 10;
+        MsbContext responderContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+        responderContext.getObjectFactory().createResponderServer(NAMESPACE, ResponderOptions.DEFAULTS, (req, ctx) -> {
+            for (int i = 0; i < responsesCount; i++) {
+                ctx.getResponder().send(RESPONSE_BODY);
+            }
+        }, String.class)
+                .listen();
+
+        MsbContext requesterContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+
+        int timeoutMs = 5000;
+        CountDownLatch latch = new CountDownLatch(responsesCount);
+
+        RequestOptions requestOptions = new RequestOptions.Builder()
+                .withWaitForResponses(10)
+                .withResponseTimeout(timeoutMs)
+                .build();
+
+        int delay = (timeoutMs / responsesCount) * 2;
+        requesterContext.getObjectFactory()
+                .createRequester(NAMESPACE, requestOptions, String.class)
+                .onResponse((resp, ctx) -> {
+                    //slow response handler
+                    try {
+                        TimeUnit.MILLISECONDS.sleep(delay);
+                    } catch (InterruptedException irrelevant) {
+                        //do nothing
+                    }
+                    latch.countDown();
+                })
+                .publish(REQUEST_BODY);
+
+        double latencyFactor = 1.5;
+        int delayInMicroseconds = delay * 1000;
+        assertTrue("Not all responses processed in time", latch.await((int) (delayInMicroseconds * responsesCount * latencyFactor), TimeUnit.MICROSECONDS));
+    }
+
+    @Test
+    public void messageWithForwardNamespace() throws Exception {
+
+        String forwardNamespace = "test:forward";
+        MsbContext responderContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+
+        CountDownLatch latch = new CountDownLatch(1);
+        responderContext.getObjectFactory().createResponderServer(NAMESPACE, ResponderOptions.DEFAULTS,
+                (req, ctx) -> {
+                    String receivedForwardNamespace = ctx.getOriginalMessage().getTopics().getForward();
+                    if (forwardNamespace.equals(receivedForwardNamespace)) {
+                        latch.countDown();
+                    }
+                }, String.class)
+                .listen();
+
+        MsbContext requesterContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+
+        RequestOptions requestOptions = new RequestOptions.Builder().withForwardNamespace(forwardNamespace).build();
+        requesterContext.getObjectFactory()
+                .createRequesterForFireAndForget(NAMESPACE, requestOptions)
+                .publish(REQUEST_BODY);
+
+        assertTrue("Message with expected forward topic not received in time", latch.await(1, TimeUnit.SECONDS));
+    }
+}
\ No newline at end of file
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RoutingKeysTest.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RoutingKeysTest.java
index 8bef51a9..d6ac8d8a 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RoutingKeysTest.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RoutingKeysTest.java
@@ -8,10 +8,7 @@
 import org.junit.After;
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CountDownLatch;
@@ -27,12 +24,9 @@ public class RoutingKeysTest {
 
     private static final String NAMESPACE = "test:routing";
 
-    final List contexts = new ArrayList<>();
-
     @After
     public void tearDown() throws Exception {
-        contexts.forEach(MsbContext::shutdown);
-        contexts.clear();
+        MsbTestHelper.getInstance().shutdownAll();
     }
 
     @Test
@@ -145,13 +139,7 @@ private Consumer arrivingMessagesChecker(ConcurrentMap getStepInstances() {
-        List steps = new ArrayList<>();
-        steps.addAll(Arrays.asList(
-                new ConfigurationSteps(),
-                new RequesterResponderSteps(),
-                new LoggerSteps()
-        ));
-        return steps;
-    }
-
-    @Override
-    public Configuration configuration() {
-        return new MostUsefulConfiguration()
-                .useStoryLoader(new LoadFromURL())
-                .useStoryReporterBuilder(new StoryReporterBuilder()
-                        .withDefaultFormats().withFormats(Format.CONSOLE, Format.HTML)
-                        .withFailureTrace(true));
-    }
-
-    @Override
-    protected List storyPaths() {
-        return new StoryFinder().findPaths(STORY_PATH, Arrays.asList(STORY_PATTERN), Arrays.asList(""), getClass().getResource("/").toString());
-    }
-
-    @Override
-    public InjectableStepsFactory stepsFactory() {
-        return new InstanceStepsFactory(configuration(), getStepInstances());
-    }
-}
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
deleted file mode 100644
index cfb7b64a..00000000
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/ConfigurationSteps.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package io.github.tcdl.msb.acceptance.bdd.steps;
-
-import com.typesafe.config.Config;
-import com.typesafe.config.ConfigFactory;
-import com.typesafe.config.ConfigValueFactory;
-import io.github.tcdl.msb.acceptance.MsbTestHelper;
-import org.jbehave.core.annotations.Given;
-import org.jbehave.core.annotations.Then;
-import org.jbehave.core.annotations.When;
-
-/**
- * Steps to manipulate with MSB configuration
- */
-public class ConfigurationSteps {
-
-    protected final MsbTestHelper helper = MsbTestHelper.getInstance();
-
-    private String MSB_CONFIG_ROOT = "msbConfig";
-    private String VALIDATE_MESSAGE = MSB_CONFIG_ROOT + ".validateMessage";
-
-    private String MSB_BROKER_CONFIG_ROOT = "msbConfig.brokerConfig";
-    private String MSB_THREADING_CONFIG_ROOT = "msbConfig.threadingConfig";
-    private String MSB_BROKER_CONSUMER_THREAD_POOL_SIZE = MSB_THREADING_CONFIG_ROOT + ".consumerThreadPoolSize";
-    private String MSB_BROKER_CONSUMER_THREAD_POOL_PREFETCH_COUNT = MSB_BROKER_CONFIG_ROOT + ".prefetchCount";
-
-    private Config config = ConfigFactory.load();
-
-    @Given("MSB configuration with validate message $validate")
-    public void initWithValidateMessage(boolean validate) {
-        config = config.withValue(VALIDATE_MESSAGE, ConfigValueFactory.fromAnyRef(validate));
-    }
-
-    @Given("MSB configuration with consumer thread pool size $size")
-    public void initWithConsumerThreadPoolSize(int size) {
-        config = config.withValue(MSB_BROKER_CONSUMER_THREAD_POOL_SIZE, ConfigValueFactory.fromAnyRef(size));
-    }
-
-    @Given("MSB configuration with consumer prefetch count $count")
-    public void initWithConsumerPrefetchCount(int capacity) {
-        config = config.withValue(MSB_BROKER_CONSUMER_THREAD_POOL_PREFETCH_COUNT, ConfigValueFactory.fromAnyRef(capacity));
-    }
-
-
-    @Given("start MSB")
-    public void initMSB() {
-        helper.initWithConfig(config);
-    }
-
-    @Then("shutdown MSB")
-    public void shutdownMSB() {
-        helper.shutdown();
-    }
-
-}
-
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java
deleted file mode 100644
index 05de90e5..00000000
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/LoggerSteps.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package io.github.tcdl.msb.acceptance.bdd.steps;
-
-
-import io.github.tcdl.msb.acceptance.bdd.util.TestOutputStreamAppender;
-import org.jbehave.core.annotations.Given;
-import org.jbehave.core.annotations.Then;
-import org.junit.Assert;
-
-public class LoggerSteps {
-
-    @Then("log contains '$substring'")
-    public void logContains(String substring) throws Exception {
-        Assert.assertTrue("String not found '" + substring + "'", TestOutputStreamAppender.isPresent(substring, 5, 500));
-    }
-
-    @Given("clear log")
-    public void clearLog() throws Exception {
-        TestOutputStreamAppender.reset();
-    }
-}
diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
deleted file mode 100644
index bb5678b9..00000000
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/bdd/steps/RequesterResponderSteps.java
+++ /dev/null
@@ -1,325 +0,0 @@
-package io.github.tcdl.msb.acceptance.bdd.steps;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.github.tcdl.msb.acceptance.MsbTestHelper;
-import io.github.tcdl.msb.api.*;
-import io.github.tcdl.msb.api.message.Message;
-import io.github.tcdl.msb.api.message.Topics;
-import io.github.tcdl.msb.api.message.payload.RestPayload;
-import io.github.tcdl.msb.support.Utils;
-import org.hamcrest.Matchers;
-import org.jbehave.core.annotations.BeforeScenario;
-import org.jbehave.core.annotations.Given;
-import org.jbehave.core.annotations.Then;
-import org.jbehave.core.annotations.When;
-import org.jbehave.core.model.ExamplesTable;
-import org.jbehave.core.model.OutcomesTable;
-import org.junit.Assert;
-
-import java.util.*;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static io.github.tcdl.msb.acceptance.MsbTestHelper.DEFAULT_CONTEXT_NAME;
-import static org.junit.Assert.*;
-
-/**
- * Steps to send requests and respond with predefined responses
- */
-public class RequesterResponderSteps {
-
-    protected final MsbTestHelper helper = MsbTestHelper.getInstance();
-
-    private volatile Requester requester;
-    private volatile String responseBody;
-    private volatile Map receivedResponse;
-    private volatile CompletableFuture> receivedResponseFuture;
-    private volatile CountDownLatch responseCountDown;
-    private volatile AtomicInteger countRequestsReceived;
-    private volatile AtomicInteger countResponsesReceived;
-    private volatile Optional nextRequestAckType = Optional.empty();
-    private volatile Optional defaultRequestsAckType = Optional.empty();
-    private volatile boolean isResponseInNewThread = false;
-    private volatile int responseProcessingDelay;
-    private volatile int responsesToSendCount;
-    private volatile int responsesToExpectCount;
-    private volatile String latestForwardNamespace = null;
-    private final LinkedList> responses = new LinkedList<>();
-    private final Deque rawIncomingMessages = new ConcurrentLinkedDeque<>();
-
-    // responder steps
-    @Given("responder server listens on namespace $namespace")
-    public void createResponderServer(String namespace) {
-        beforeCreateResponder();
-        ObjectMapper mapper = helper.getPayloadMapper(DEFAULT_CONTEXT_NAME);
-        helper.createResponderServer(DEFAULT_CONTEXT_NAME, namespace, (request, responderContext) -> {
-            if (responseBody == null) {
-                return;
-            }
-
-            countRequestsReceived.incrementAndGet();
-
-            Runnable responseActions = () -> {
-                boolean isSendResponse = true;
-                String ackType = nextRequestAckType.orElseGet(
-                        () -> defaultRequestsAckType.orElseGet(
-                                () -> "auto"));
-
-                switch (ackType) {
-                    case "confirm":
-                        responderContext.getAcknowledgementHandler().confirmMessage();
-                        break;
-                    case "reject":
-                        responderContext.getAcknowledgementHandler().rejectMessage();
-                        isSendResponse = false;
-                        break;
-                    case "retry":
-                        responderContext.getAcknowledgementHandler().retryMessage();
-                        isSendResponse = false;
-                        break;
-                }
-
-                nextRequestAckType = Optional.empty();
-                latestForwardNamespace = responderContext.getOriginalMessage().getTopics().getForward();
-                if (isSendResponse) {
-                    RestPayload payload = new RestPayload.Builder()
-                            .withBody(Utils.fromJson(responseBody, Map.class, mapper))
-                            .build();
-                    for (int i = 0; i < responsesToSendCount; i++) {
-                        responderContext.getResponder().send(payload);
-                    }
-                }
-            };
-
-            if (isResponseInNewThread) {
-                responderContext.getAcknowledgementHandler().setAutoAcknowledgement(false);
-                new Thread(responseActions).run();
-            } else {
-                responseActions.run();
-            }
-        }).listen();
-    }
-
-    private void beforeCreateResponder() {
-        responseCountDown = null;
-        countRequestsReceived = new AtomicInteger(0);
-        countResponsesReceived = new AtomicInteger(0);
-        responseProcessingDelay = 0;
-        responsesToSendCount = 1;
-        responsesToExpectCount = 1;
-        nextRequestAckType = Optional.empty();
-        defaultRequestsAckType = Optional.empty();
-        isResponseInNewThread = false;
-    }
-
-    @Given("responder server will $nextRequestAckType next request")
-    public void setNextRequestAckType(String nextRequestAckType) throws Exception {
-        this.nextRequestAckType = Optional.of(nextRequestAckType);
-    }
-
-    @Given("responder server will $allRequestsAckType all requests")
-    public void setDefaultRequestsAckType(String allRequestsAckType) throws Exception {
-        this.defaultRequestsAckType = Optional.of(allRequestsAckType);
-    }
-
-    @Given("requester will process responses with $timeout ms delay")
-    public void setesponseDelay(int responseDelay) throws Exception {
-        this.responseProcessingDelay = responseDelay;
-    }
-
-    @Given("responder will provide $responseCount responses")
-    public void setResponsesToSendCount(int responsesToSendCount) throws Exception {
-        this.responsesToSendCount = responsesToSendCount;
-    }
-
-    @Given("responder server will send acknowledge and response from a new thread")
-    public void setResponseInNewThread() throws Exception {
-        isResponseInNewThread = true;
-    }
-
-    @Given("responder server responds with '$body'")
-    @When("responder server responds with '$body'")
-    public void respond(String body) {
-        responseBody = body;
-    }
-
-    // requester steps
-    @Given("requester sends requests to namespace $namespace")
-    public void createRequester(String namespace) {
-        requester = helper.createRequester(DEFAULT_CONTEXT_NAME, namespace, null, 1, RestPayload.class);
-    }
-
-    // requester steps
-    @Given("requester sets forwarding to $forwardNamespace and target namespace to $namespace")
-    public void createRequesterWithForwarding(String forwardNamespace, String namespace) {
-        requester = helper.createRequester(DEFAULT_CONTEXT_NAME, namespace, forwardNamespace, 0, RestPayload.class);
-    }
-
-    // requester steps
-    @Given("requester (with $requestTimeout ms request timeout to receive $responseCount responses) sends requests to namespace $namespace")
-    public void createRequester(int requestTimeout, int responseCount, String namespace) {
-        responseCountDown = new CountDownLatch(responseCount);
-        responsesToExpectCount = responseCount;
-        requester = helper.createRequester(DEFAULT_CONTEXT_NAME, namespace, null, responsesToExpectCount, 100, requestTimeout, RestPayload.class);
-    }
-
-    @When("requester sends a request")
-    public void sendRequest() throws Exception {
-        sendRequest(DEFAULT_CONTEXT_NAME);
-    }
-
-    private void sendRequest(String contextName) throws Exception {
-        onBeforeRequest();
-        RestPayload payload = helper.createFacetParserPayload("QUERY", null);
-        helper.sendRequest(requester, payload, responsesToExpectCount, this::onResponse, this::onEnd);
-    }
-
-    @When("requester sends a request with tag '$tag'")
-    public void sendRequestWithTag(String tag) throws Exception {
-        onBeforeRequest();
-        RestPayload payload = helper.createFacetParserPayload("QUERY", null);
-        helper.sendRequest(requester, payload, responsesToExpectCount, this::onResponse, this::onEnd, tag);
-    }
-
-    @When("^requester sends a request with body '$body'$")
-    public void sendRequestWithBody(String body) throws Exception {
-        onBeforeRequest();
-        RestPayload payload = helper.createFacetParserPayload(null, body);
-        helper.sendRequest(requester, payload, true, responsesToExpectCount, null, this::onResponse, this::onEnd);
-    }
-
-    @Given("responder server listens on fanout namespace $namespace")
-    public void subscribeResponder(String namespace) {
-        MsbContext context = helper.getDefaultContext();
-        context.getObjectFactory().createResponderServer(namespace, new MessageTemplate(),
-                (request, responderContext) -> {
-                    rawIncomingMessages.add(responderContext.getOriginalMessage());
-                }, String.class).listen();
-    }
-
-    private void onBeforeRequest() {
-        receivedResponse = null;
-        receivedResponseFuture = new CompletableFuture<>();
-    }
-
-    private void onResponse(RestPayload> payload) {
-        if (responseProcessingDelay > 0) {
-            try {
-                Thread.sleep(responseProcessingDelay);
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        if (responseCountDown != null) {
-            responseCountDown.countDown();
-        }
-
-        countResponsesReceived.incrementAndGet();
-        receivedResponseFuture.complete(payload.getBody());
-    }
-
-    private void onEnd(Void in) {
-        if (responseCountDown != null && responseCountDown.getCount() > 0) {
-            fail("onEnd has been executed while not all responses were received yet, pending responses count: " + responseCountDown.getCount());
-        }
-    }
-
-    @Then("requester gets response in $timeout ms")
-    public void waitForResponse(long timeout) throws Exception {
-        try {
-            receivedResponse = receivedResponseFuture.get(timeout, TimeUnit.MILLISECONDS);
-        } catch (TimeoutException timeoutException) {
-            fail("Response has not been received during a timeout");
-        }
-        Assert.assertNotNull("Response received is null", receivedResponse);
-    }
-
-    @Then("requester will get all responses in $timeout ms")
-    public void waitForAllResponses(long timeout) throws Exception {
-        if (!responseCountDown.await(timeout, TimeUnit.MILLISECONDS)) {
-            fail("All responses has not been received during a timeout, pending count: " + responseCountDown.getCount());
-        }
-    }
-
-    @Then("requester does not get a response in $timeout ms")
-    public void waitForNoResponse(long timeout) throws Exception {
-        try {
-            receivedResponse = receivedResponseFuture.get(timeout, TimeUnit.MILLISECONDS);
-        } catch (TimeoutException timeoutException) {
-            //ok
-        }
-        Assert.assertNull("Unexpected response received", receivedResponse);
-    }
-
-    @Then("response equals $table")
-    public void responseEquals(ExamplesTable table) throws Exception {
-        Map expected = table.getRow(0);
-        OutcomesTable outcomes = new OutcomesTable();
-
-        for (String key : expected.keySet()) {
-            outcomes.addOutcome(key, receivedResponse.get(key), Matchers.equalTo(expected.get(key)));
-        }
-
-        outcomes.verify();
-    }
-
-    @Then("request forward namespace equals $forwardNamespace")
-    public void responseEquals(String forwardNamespace) throws Exception {
-        assertEquals(forwardNamespace, latestForwardNamespace);
-    }
-
-
-    @Then("message envelope topics section is $table")
-    public void checkTopicsSection(ExamplesTable table) throws InterruptedException {
-        TimeUnit.SECONDS.sleep(1);
-        Message lastMessage = rawIncomingMessages.pollLast();
-
-        Map expected = table.getRow(0);
-        String expectedTo = normalizeNull(expected.get("to"));
-        String expectedForward = normalizeNull(expected.get("forward"));
-        String expectedResponse = normalizeNull(expected.get("response"));
-        String expectedRoutingKey = normalizeNull(expected.get("routingKey"));
-
-        Topics topics = lastMessage.getTopics();
-        assertEquals("Invalid value of topic 'to'", expectedTo, topics.getTo());
-        assertEquals("Invalid value of topic 'forward'", expectedForward, topics.getForward());
-        assertEquals("Invalid value of topic 'response'", expectedResponse, topics.getResponse());
-        assertEquals("Invalid value of field 'routingKey'", expectedRoutingKey, topics.getRoutingKey());
-    }
-
-    private String normalizeNull(String string) {
-        if (string != null && string.trim().equalsIgnoreCase("null")) {
-            return null;
-        } else {
-            return string;
-        }
-    }
-
-    @Then("responder requests received count equals $expectedRequestsReceivedCount")
-    public void requestCountEquals(int expectedCountRequestsReceived) throws Exception {
-        assertEquals(expectedCountRequestsReceived, countRequestsReceived.get());
-    }
-
-    @Then("requester responses received count equals $expectedRequestsReceivedCount")
-    public void responseCountEquals(int expectedCountResponsesReceived) throws Exception {
-        assertEquals(expectedCountResponsesReceived, countResponsesReceived.get());
-    }
-
-    @Then("response contains $table")
-    public void responseContains(ExamplesTable table) throws Exception {
-        Map expected = table.getRow(0);
-        OutcomesTable outcomes = new OutcomesTable();
-
-        for (String key : expected.keySet()) {
-            outcomes.addOutcome(key, receivedResponse.get(key).toString(), Matchers.containsString(expected.get(key)));
-        }
-
-        outcomes.verify();
-    }
-
-    @BeforeScenario
-    public void resetMockResponses() {
-        responses.clear();
-    }
-}
diff --git a/acceptance/src/test/resources/scenarios/requester_responder.story b/acceptance/src/test/resources/scenarios/requester_responder.story
deleted file mode 100644
index e01d4dc5..00000000
--- a/acceptance/src/test/resources/scenarios/requester_responder.story
+++ /dev/null
@@ -1,134 +0,0 @@
-Lifecycle:
-Before:
-Given MSB configuration with consumer prefetch count 20
-And start MSB
-And clear log
-And reset mock responses
-After:
-Outcome: ANY
-Then shutdown MSB
-
-Scenario: Sends a request to a responder server and waits for response
-
-Given responder server responds with '{"result": "hello jbehave"}'
-And responder server listens on namespace test:jbehave
-And requester sends requests to namespace test:jbehave
-When requester sends a request
-Then requester gets response in 5000 ms
-And responder requests received count equals 1
-And response equals
-|result|
-|hello jbehave|
-
-Scenario: Sends a request to a responder server with a custom tag
-
-Given responder server responds with '{"result": "hello jbehave"}'
-And responder server listens on namespace test:jbehave
-And requester sends requests to namespace test:jbehave
-When requester sends a request with tag 'customTagKey:MY_CUSTOM_TAG'
-Then requester gets response in 5000 ms
-And responder requests received count equals 1
-And response equals
-|result|
-|hello jbehave|
-And log contains 'tags[customTagKey:MY_CUSTOM_TAG]'
-And log contains 'customTagKey[MY_CUSTOM_TAG]'
-
-Scenario: Responder ask to retry a first message delivery so it will be redelivered
-
-Given responder server responds with '{"result": "hello jbehave - manual retry"}'
-And responder server listens on namespace test:jbehave
-And responder server will confirm all requests
-And responder server will retry next request
-And requester sends requests to namespace test:jbehave
-When requester sends a request
-Then requester gets response in 5000 ms
-And responder requests received count equals 2
-And response equals
-|result|
-|hello jbehave - manual retry|
-
-
-Scenario: Responder confirms all incoming messages manually
-
-Given responder server responds with '{"result": "hello jbehave - manual confirm"}'
-And responder server listens on namespace test:jbehave
-And responder server will confirm all requests
-And requester sends requests to namespace test:jbehave
-When requester sends a request
-Then requester gets response in 5000 ms
-And responder requests received count equals 1
-And response equals
-|result|
-|hello jbehave - manual confirm|
-
-
-Scenario: Responder retrys all incoming messages manually
-
-Given responder server responds with '{"result": "hello jbehave - constant retry"}'
-And responder server listens on namespace test:jbehave
-And responder server will retry all requests
-And requester sends requests to namespace test:jbehave
-When requester sends a request
-Then requester does not get a response in 1000 ms
-And responder requests received count equals 2
-
-
-Scenario: Responder rejctes all incoming messages manually
-
-Given responder server responds with '{"result": "hello jbehave - constant reject"}'
-And responder server listens on namespace test:jbehave
-And responder server will reject all requests
-And requester sends requests to namespace test:jbehave
-When requester sends a request
-Then requester does not get a response in 1000 ms
-And responder requests received count equals 1
-
-
-Scenario: Responder confirms all incoming messages manually in a different thread
-
-Given responder server responds with '{"result": "hello jbehave - manual confirm"}'
-And responder server listens on namespace test:jbehave
-And responder server will confirm all requests
-And responder server will send acknowledge and response from a new thread
-And requester sends requests to namespace test:jbehave
-When requester sends a request
-Then requester gets response in 5000 ms
-And responder requests received count equals 1
-And response equals
-|result|
-|hello jbehave - manual confirm|
-
-
-Scenario: Responder retrys all incoming messages manually in a different thread
-
-Given responder server responds with '{"result": "hello jbehave - constant retry"}'
-And responder server listens on namespace test:jbehave
-And responder server will retry all requests
-And responder server will send acknowledge and response from a new thread
-And requester sends requests to namespace test:jbehave
-When requester sends a request
-Then requester does not get a response in 1000 ms
-And responder requests received count equals 2
-
-Scenario: Requester processes callback with a delay
-
-Given responder server responds with '{"result": "hello jbehave"}'
-And responder server listens on namespace test:jbehave
-And responder will provide 10 responses
-And requester (with 1000 ms request timeout to receive 10 responses) sends requests to namespace test:jbehave
-And requester will process responses with 200 ms delay
-When requester sends a request
-Then requester will get all responses in 5000 ms
-And responder requests received count equals 1
-
-
-Scenario: Message forwarding
-
-Given responder server responds with '{"result": "hello jbehave - forwarding"}'
-And responder server listens on namespace test:jbehave
-And requester sets forwarding to test:jbehave:forwarding and target namespace to test:jbehave
-When requester sends a request
-Then requester gets response in 5000 ms
-And responder requests received count equals 1
-And request forward namespace equals test:jbehave:forwarding
diff --git a/pom.xml b/pom.xml
index 636f949e..ba124a17 100644
--- a/pom.xml
+++ b/pom.xml
@@ -110,11 +110,6 @@
                 assertj-core
                 3.3.0
             
-            
-                org.jbehave
-                jbehave-core
-                4.0.4
-            
             
                 com.fasterxml.jackson.datatype
                 jackson-datatype-jsr310

From f06b662d1b723a70179a48d7aea8892e9262c6e8 Mon Sep 17 00:00:00 2001
From: rdro-tc 
Date: Mon, 5 Dec 2016 16:20:41 +0200
Subject: [PATCH 178/226] IN-1285: replaced default connection exception
 handler

---
 .../msb/adapters/amqp/AmqpAdapterFactory.java   |  3 ++-
 .../msb/adapters/amqp/AmqpExceptionHandler.java | 17 +++++++++++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)
 create mode 100644 amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpExceptionHandler.java

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
index 6c931375..681d6e57 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
@@ -6,7 +6,6 @@
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
 import io.github.tcdl.msb.adapters.AdapterFactory;
-import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.exception.AdapterCreationException;
 import io.github.tcdl.msb.api.exception.ChannelException;
@@ -118,6 +117,8 @@ protected ConnectionFactory createConnectionFactory(AmqpBrokerConfig adapterConf
         connectionFactory.setAutomaticRecoveryEnabled(true);
         connectionFactory.setNetworkRecoveryInterval(adapterConfig.getNetworkRecoveryIntervalMs());
         connectionFactory.setRequestedHeartbeat(adapterConfig.getHeartbeatIntervalSec());
+        connectionFactory.setExceptionHandler(new AmqpExceptionHandler());
+
         if (username.isPresent()) {
             connectionFactory.setUsername(username.get());
         }
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpExceptionHandler.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpExceptionHandler.java
new file mode 100644
index 00000000..7949ddc7
--- /dev/null
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpExceptionHandler.java
@@ -0,0 +1,17 @@
+package io.github.tcdl.msb.adapters.amqp;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.impl.DefaultExceptionHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AmqpExceptionHandler extends DefaultExceptionHandler {
+
+    private static Logger LOG = LoggerFactory.getLogger(AmqpExceptionHandler.class);
+
+    @Override
+    protected void handleChannelKiller(Channel channel, Throwable exception, String what) {
+        LOG.error("{} threw exception for channel '{}'.", what, channel, exception);
+        super.handleChannelKiller(channel, exception, what);
+    }
+}

From 80026627acaa4b27623f8d4eb32d208387ac006b Mon Sep 17 00:00:00 2001
From: alex 
Date: Thu, 15 Dec 2016 13:34:57 +0200
Subject: [PATCH 179/226] Removed redundant auto recovery logic in order to fix
 buggy idle channels spawning.

---
 .../amqp/AmqpAutoRecoveringChannel.java       | 136 ------------------
 .../adapters/amqp/AmqpProducerAdapter.java    |   9 +-
 .../msb/adapters/amqp/LoggingAmqpChannel.java |  83 +++++++++++
 amqp/src/main/resources/amqp.conf             |   2 +-
 .../amqp/AmqpAutoRecoveringChannelTest.java   |  57 --------
 5 files changed, 88 insertions(+), 199 deletions(-)
 delete mode 100644 amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java
 create mode 100644 amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/LoggingAmqpChannel.java
 delete mode 100644 amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannelTest.java

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java
deleted file mode 100644
index 9be3dd03..00000000
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannel.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package io.github.tcdl.msb.adapters.amqp;
-
-import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.ConfirmListener;
-import io.github.tcdl.msb.api.exception.ChannelException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-/**
- * Wrapper for {@link Channel} that support automatic re-initialization upon errors.
- */
-public class AmqpAutoRecoveringChannel {
-    private static final Logger LOG = LoggerFactory.getLogger(AmqpAutoRecoveringChannel.class);
-
-    private AmqpConnectionManager connectionManager;
-
-    /**
-     * Lock object used for 2 purposes:
-     * 1. Prevent interleaving of basicPublish with confirmSelect
-     * 2. Prevent interleaving of channel initialization and shutdown
-     */
-    private final AtomicReference channelReference = new AtomicReference<>();
-
-    public AmqpAutoRecoveringChannel(AmqpConnectionManager connectionManager) {
-        this.connectionManager = connectionManager;
-    }
-
-    public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map arguments) {
-        LOG.debug("Declaring exchange. Name = [{}], type = [{}], durable = [{}], autoDelete = [{}], args = [{}].",
-                exchange, type, durable, autoDelete, arguments);
-
-        return atomicOperationOnChannel(channel -> {
-            try {
-                return channel.exchangeDeclare(exchange, type, durable, autoDelete, arguments);
-            } catch (IOException e) {
-                throw new ChannelException("exchange.declare call failed", e);
-            }
-        });
-    }
-
-    public void basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body) {
-        LOG.debug("Publishing message. Exchange name = [{}], routing key = [{}]", exchange, routingKey);
-        atomicOperationOnChannel(channel -> {
-            try {
-                channel.basicPublish(exchange, routingKey, props, body);
-            } catch (IOException e) {
-                throw new ChannelException("basic.publish call failed", e);
-            }
-        });
-    }
-
-    private  T atomicOperationOnChannel(Function operation) {
-        synchronized (channelReference) {
-            Channel channel = getOpenChannel();
-            return operation.apply(channel);
-        }
-    }
-
-    private void atomicOperationOnChannel(Consumer operation) {
-        synchronized (channelReference) {
-            Channel channel = getOpenChannel();
-            operation.accept(channel);
-        }
-    }
-
-    private Channel getOpenChannel() {
-        return channelReference.updateAndGet(currentChannel -> {
-            if (currentChannel == null) {
-                return newChannelWithPublisherConfirms();
-            } else if (!currentChannel.isOpen()) {
-                closeChannel(currentChannel);
-                return newChannelWithPublisherConfirms();
-            } else {
-                return currentChannel;
-            }
-        });
-    }
-
-    private Channel newChannelWithPublisherConfirms() {
-        Channel newChannel;
-        try {
-            newChannel = connectionManager.obtainConnection().createChannel();
-            newChannel.confirmSelect();
-        } catch (IOException e) {
-            throw new ChannelException("Channel creation failed", e);
-        }
-
-        newChannel.addConfirmListener(new ConfirmListener() {
-            @Override
-            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
-                LOG.debug("Processing publisher ack (deliveryTag = {}, multiple = {})", deliveryTag, multiple);
-            }
-
-            @Override
-            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
-                LOG.debug("Processing publisher nack (deliveryTag = {}, multiple = {})", deliveryTag, multiple);
-            }
-        });
-
-        newChannel.addShutdownListener(cause -> {
-            LOG.debug("Handling channel shutdown...");
-            if (cause.isInitiatedByApplication()) {
-                LOG.debug("Shutdown is initiated by application. Ignoring it.");
-            } else {
-                LOG.error("Shutdown is NOT initiated by application. Resetting the channel.", cause);
-                /*
-                We cannot re-initialize channel here directly because ShutdownListener callbacks run in the connection's thread,
-                so the call to createChannel causes a deadlock since it blocks waiting for a response (whilst the connection's thread
-                is stuck executing the listener).
-                 */
-
-                Channel oldChannel = channelReference.getAndSet(null);
-                closeChannel(oldChannel);
-            }
-        });
-
-        return newChannel;
-    }
-
-    private void closeChannel(Channel channel) {
-        try {
-            LOG.debug("Closing channel. Channel number = [{}]", channel.getChannelNumber());
-            channel.abort();
-            LOG.debug("Channel [{}] closed.", channel.getChannelNumber());
-        } catch (IOException e) {
-            LOG.info("Error closing AMQP channel", e.getCause());
-        }
-    }
-}
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
index f1ab7657..10ecb2ee 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
@@ -8,14 +8,13 @@
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
 
-import java.io.IOException;
 import java.nio.charset.Charset;
 
 public class AmqpProducerAdapter implements ProducerAdapter {
 
     final String exchangeName;
     final AmqpBrokerConfig amqpBrokerConfig;
-    final AmqpAutoRecoveringChannel amqpAutoRecoveringChannel;
+    final LoggingAmqpChannel channel;
 
     public AmqpProducerAdapter(String topic, ExchangeType exchangeType, AmqpBrokerConfig amqpBrokerConfig, AmqpConnectionManager connectionManager) {
         Validate.notNull(topic, "Topic is mandatory");
@@ -25,10 +24,10 @@ public AmqpProducerAdapter(String topic, ExchangeType exchangeType, AmqpBrokerCo
 
         this.exchangeName = topic;
         this.amqpBrokerConfig = amqpBrokerConfig;
-        this.amqpAutoRecoveringChannel = new AmqpAutoRecoveringChannel(connectionManager);
+        this.channel = LoggingAmqpChannel.instance(connectionManager);
 
         try {
-            amqpAutoRecoveringChannel.exchangeDeclare(exchangeName, exchangeType.value(), false /* durable */, true /* auto-delete */, null);
+            channel.exchangeDeclare(exchangeName, exchangeType.value(), false /* durable */, true /* auto-delete */, null);
         } catch (Exception e) {
             throw new ChannelException("Failed to setup channel from ActiveMQ connection", e);
         }
@@ -48,7 +47,7 @@ public void publish(String jsonMessage, String routingKey) {
         Charset charset = amqpBrokerConfig.getCharset();
 
         try {
-            amqpAutoRecoveringChannel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_BASIC, jsonMessage.getBytes(charset));
+            channel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_BASIC, jsonMessage.getBytes(charset));
         } catch (Exception e) {
             throw new ChannelException(String.format("Failed to publish message '%s' into exchange '%s' with routing key '%s'", jsonMessage, exchangeName, routingKey), e);
         }
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/LoggingAmqpChannel.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/LoggingAmqpChannel.java
new file mode 100644
index 00000000..5e3d3713
--- /dev/null
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/LoggingAmqpChannel.java
@@ -0,0 +1,83 @@
+package io.github.tcdl.msb.adapters.amqp;
+
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.ConfirmListener;
+import io.github.tcdl.msb.api.exception.ChannelException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Wrapper for {@link Channel} that support automatic re-initialization upon errors.
+ */
+public class LoggingAmqpChannel {
+    private static final Logger LOG = LoggerFactory.getLogger(LoggingAmqpChannel.class);
+
+    private AmqpConnectionManager connectionManager;
+    private Channel channel;
+
+    public static LoggingAmqpChannel instance(AmqpConnectionManager connectionManager){
+        LoggingAmqpChannel loggingChannel = new LoggingAmqpChannel(connectionManager);
+        loggingChannel.init();
+        return loggingChannel;
+    }
+
+    private LoggingAmqpChannel(AmqpConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+    }
+
+    /*
+     * Initialization logic resides in this method instead of constructor in order to avoid 'this' reference escape
+     * while object construction is not yet finished.
+     */
+    private void init() {
+        try {
+            this.channel = connectionManager.obtainConnection().createChannel();
+        } catch (IOException e) {
+            throw new ChannelException("Channel creation failed with exception", e);
+        }
+
+        channel.addConfirmListener(new ConfirmListener() {
+            @Override
+            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
+                LOG.debug("Processing publisher ack (deliveryTag = {}, multiple = {})", deliveryTag, multiple);
+            }
+
+            @Override
+            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
+                LOG.debug("Processing publisher nack (deliveryTag = {}, multiple = {})", deliveryTag, multiple);
+            }
+        });
+
+        channel.addShutdownListener(cause -> {
+            LOG.debug("Handling channel shutdown...");
+            if (cause.isInitiatedByApplication()) {
+                LOG.debug("Shutdown is initiated by application.");
+            } else {
+                LOG.error("Shutdown is NOT initiated by application.", cause);
+            }
+        });
+    }
+
+    public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map arguments) {
+        LOG.debug("Declaring exchange. Name = [{}], type = [{}], durable = [{}], autoDelete = [{}], args = [{}].",
+                exchange, type, durable, autoDelete, arguments);
+        try {
+            return channel.exchangeDeclare(exchange, type, durable, autoDelete, arguments);
+        } catch (IOException e) {
+            throw new ChannelException("exchange.declare call failed", e);
+        }
+    }
+
+    public void basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body) {
+        LOG.debug("Publishing message. Exchange name = [{}], routing key = [{}]", exchange, routingKey);
+        try {
+            channel.basicPublish(exchange, routingKey, props, body);
+        } catch (IOException e) {
+            throw new ChannelException("basic.publish call failed", e);
+        }
+    }
+}
diff --git a/amqp/src/main/resources/amqp.conf b/amqp/src/main/resources/amqp.conf
index 9c1c44fe..e9b0a315 100644
--- a/amqp/src/main/resources/amqp.conf
+++ b/amqp/src/main/resources/amqp.conf
@@ -28,7 +28,7 @@ config.amqp = {
   # Interval of the heartbeats that are used to detect broken connections. Zero for none. See for more details: https://www.rabbitmq.com/heartbeats.html
   heartbeatIntervalSec = 30
   # Interval of connection recovery attempts. See for more details: https://www.rabbitmq.com/api-guide.html#connection-recovery
-  networkRecoveryIntervalMs = 5000
+  networkRecoveryIntervalMs = 500
   
   # Specify the size of the limit of unacknowledged messages on a queue basis
   prefetchCount = 10
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannelTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannelTest.java
deleted file mode 100644
index fc37d5d6..00000000
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpAutoRecoveringChannelTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package io.github.tcdl.msb.adapters.amqp;
-
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-
-import java.util.Collections;
-
-import static org.mockito.Mockito.*;
-
-@RunWith(MockitoJUnitRunner.class)
-public class AmqpAutoRecoveringChannelTest {
-
-    @Mock
-    AmqpConnectionManager connectionManager;
-    @Mock
-    Connection connection;
-    @Mock
-    Channel firstChannel, secondChannel;
-
-    AmqpAutoRecoveringChannel autoRecoveringChannel;
-
-    @Before
-    public void setUp() throws Exception {
-        when(connection.createChannel()).thenReturn(firstChannel);
-        when(connectionManager.obtainConnection()).thenReturn(connection);
-
-        autoRecoveringChannel = new AmqpAutoRecoveringChannel(connectionManager);
-    }
-
-    @Test
-    public void testNewChannelOpened() throws Exception {
-        autoRecoveringChannel.exchangeDeclare("test-exchange", "test-exchange-type", false, false, Collections.emptyMap());
-        verify(connection).createChannel();
-    }
-
-    @Test
-    public void testClosedChannel_replaced() throws Exception {
-
-        Channel secondChannel = mock(Channel.class);
-
-        when(connection.createChannel()).thenReturn(firstChannel, secondChannel);
-
-        autoRecoveringChannel.exchangeDeclare("test-exchange", "test-exchange-type", false, false, Collections.emptyMap());
-        when(firstChannel.isOpen()).thenReturn(false);
-        autoRecoveringChannel.exchangeDeclare("test-exchange", "test-exchange-type", false, false, Collections.emptyMap());
-
-        verify(connection, times(2)).createChannel();
-        verify(firstChannel).abort();
-        verify(firstChannel).exchangeDeclare(anyString(), anyString(), anyBoolean(), anyBoolean(), anyMapOf(String.class, Object.class));
-        verify(secondChannel).exchangeDeclare(anyString(), anyString(), anyBoolean(), anyBoolean(), anyMapOf(String.class, Object.class));
-    }
-}
\ No newline at end of file

From 16e003a7c4c23a2f9ab0a5a6fa9680d63e05e6bb Mon Sep 17 00:00:00 2001
From: Alexandr Zolotov 
Date: Mon, 19 Dec 2016 12:48:39 +0200
Subject: [PATCH 180/226] fixed javadoc

---
 .../io/github/tcdl/msb/adapters/amqp/LoggingAmqpChannel.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/LoggingAmqpChannel.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/LoggingAmqpChannel.java
index 5e3d3713..915b2a6c 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/LoggingAmqpChannel.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/LoggingAmqpChannel.java
@@ -11,7 +11,7 @@
 import java.util.Map;
 
 /**
- * Wrapper for {@link Channel} that support automatic re-initialization upon errors.
+ * Wrapper for {@link Channel} that provides some additional debug logging.
  */
 public class LoggingAmqpChannel {
     private static final Logger LOG = LoggerFactory.getLogger(LoggingAmqpChannel.class);

From 5c5d890d979767eed86b5f2ded7156e10eab2cd3 Mon Sep 17 00:00:00 2001
From: alex 
Date: Wed, 8 Feb 2017 14:51:46 +0200
Subject: [PATCH 181/226] Fixed message processing during context shutdown

---
 .../tcdl/msb/acceptance/ShutdownTest.java     | 136 ++++++++++++++++++
 .../src/test/resources/logback-test.xml       |   4 +-
 .../io/github/tcdl/msb/ChannelManager.java    |   2 +-
 3 files changed, 140 insertions(+), 2 deletions(-)
 create mode 100644 acceptance/src/test/java/io/github/tcdl/msb/acceptance/ShutdownTest.java

diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/ShutdownTest.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/ShutdownTest.java
new file mode 100644
index 00000000..f64112fe
--- /dev/null
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/ShutdownTest.java
@@ -0,0 +1,136 @@
+package io.github.tcdl.msb.acceptance;
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import io.github.tcdl.msb.api.MsbContext;
+import io.github.tcdl.msb.api.ResponderOptions;
+import io.github.tcdl.msb.api.ResponderServer;
+import org.junit.After;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * This test checks the case when {@link MsbContext} in shut down while message is being processed.
+ * It ensures that if message processing is successful than acknowledgement is sent to the broker.
+ * 

+ * Steps being performed: + *

    + *
  1. set up MsbContext with durable AMQP queues and exchanges
  2. + *
  3. ensure incoming queue bound to test:shutdown exchange is empty by consuming everything from it
  4. + *
  5. start ResponderServer with blocking message handler (to suspend processing)
  6. + *
  7. publish test message to invoke blocking handler
  8. + *
  9. call {@link MsbContext#shutdown()}
  10. + *
  11. unblock message handler
  12. + *
  13. check that handler was successfully executed
  14. + *
  15. run ResponderServer from new 'probe' MsbContext that generates the same queue names as the previous one and check that there are no messages in incoming queue
  16. + *
+ * There are several arbitrary delays between some steps because of reactive non-blocking API of msb-java. + * + * @author Alexandr Zolotov + */ +public class ShutdownTest { + + private static final Logger LOG = LoggerFactory.getLogger(ShutdownTest.class); + + private ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); + private Config config = ConfigFactory.load(); + + @Test(timeout = 10000) + public void testShutdown() throws Exception { + + String namespace = "test:shutdown"; + AtomicBoolean handlerFinished = new AtomicBoolean(false); + CountDownLatch handlerBlockingLatch = new CountDownLatch(1); + + MsbContext msbContext = createMsbContext(config); + emptyIncomingQueue(namespace, msbContext).get(); //block until queue is empty + + TimeUnit.MILLISECONDS.sleep(50); + msbContext.getObjectFactory().createResponderServer(namespace, ResponderOptions.DEFAULTS, + (request, responderContext) -> { + handlerBlockingLatch.await(); + handlerFinished.set(true); + }, + String.class) + .listen(); + + TimeUnit.MILLISECONDS.sleep(50); + + msbContext.getObjectFactory() + .createRequesterForFireAndForget(namespace) + .publish("very important message"); + + executor.schedule(handlerBlockingLatch::countDown, 200, TimeUnit.MILLISECONDS); + //give msb context some time to be sure it received request before shutdown + TimeUnit.MILLISECONDS.sleep(50); + msbContext.shutdown(); + + //verify handler was executed + assertTrue(handlerFinished.get()); + MsbContext probeContext = createMsbContext(config); + + CountDownLatch messageReceivedFromQueueLatch = new CountDownLatch(1); + + probeContext.getObjectFactory().createResponderServer(namespace, ResponderOptions.DEFAULTS, + (request, responderContext) -> messageReceivedFromQueueLatch.countDown(), String.class) + .listen(); + + assertFalse("Message is not expected to be present in the queue but it is", messageReceivedFromQueueLatch.await(200, TimeUnit.MILLISECONDS)); + } + + private CompletableFuture emptyIncomingQueue(String namespace, MsbContext msbContext) { + + int timeoutTillEmptyMs = 1000; + int oneTickMs = 100; + int tickMaxCount = timeoutTillEmptyMs / oneTickMs; + + CompletableFuture handle = new CompletableFuture<>(); + + AtomicInteger ticksLeft = new AtomicInteger(tickMaxCount); + ResponderServer cleanUpServer = msbContext.getObjectFactory().createResponderServer(namespace, + ResponderOptions.DEFAULTS, + (request, responderContext) -> { + LOG.info("Message received. Resetting the timer."); + ticksLeft.set(tickMaxCount);//reset + }, + String.class + ); + + executor.scheduleWithFixedDelay(() -> { + if (ticksLeft.decrementAndGet() <= 0) { + LOG.info("Queue is empty"); + cleanUpServer.stop(); + handle.complete(null); + throw new RuntimeException("poor man's way to cancel scheduled task"); + } + }, 100, oneTickMs, TimeUnit.MILLISECONDS); + + cleanUpServer.listen(); + return handle; + } + + private MsbContext createMsbContext(Config baseConfig) { + return MsbTestHelper.getInstance().initWithConfig(baseConfig); + } + + @After + public void tearDown() throws Exception { + if (executor != null) { + executor.shutdownNow(); + } + + MsbTestHelper.getInstance().shutdownAll(); + } +} diff --git a/acceptance/src/test/resources/logback-test.xml b/acceptance/src/test/resources/logback-test.xml index c523cce5..9ad5320d 100644 --- a/acceptance/src/test/resources/logback-test.xml +++ b/acceptance/src/test/resources/logback-test.xml @@ -3,7 +3,7 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %logger [%t] tags[%X{msbTags}] corrId[%X{msbCorrelationId}] customTagKey[%X{customTagKey}] - %m%n + %d{HH:mm:ss.SSS} %-5level %logger{1} [%t] tags[%X{msbTags}] corrId[%X{msbCorrelationId}] customTagKey[%X{customTagKey}] - %m%n @@ -14,6 +14,8 @@ + + diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java index a5b59e36..a228454b 100644 --- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java +++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java @@ -139,8 +139,8 @@ private Consumer createConsumer(String topic, boolean isResponseTopic, Responder public void shutdown() { LOG.info("Shutting down..."); - adapterFactory.shutdown(); messageHandlerInvoker.shutdown(); + adapterFactory.shutdown(); LOG.info("Shutdown complete"); } } From 2e6f4bafb3d75ac537ef1676010b6725de408f93 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Fri, 10 Feb 2017 17:39:26 +0200 Subject: [PATCH 182/226] Prepared for 1.6.1 release --- release-notes.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/release-notes.html b/release-notes.html index f88514d4..2b517b78 100644 --- a/release-notes.html +++ b/release-notes.html @@ -4,11 +4,16 @@ -

Welcome to MSB-Java version 1.6.0

+

Welcome to MSB-Java version 1.6.1

-

November 16, 2016

+

February 2, 2017

+Features of MSB-Java version 1.6.1:
+   - Improved message processing during context shutdown;
+   - Improved AMQP exception handling and channel automatic recovery;
+   - Removed JBehave tests as deprecated;
+   
 Features of MSB-Java version 1.6.0:
    - Broken backward compatibility with 1.5.2 version. Now exchange type for AMQ is not determined by routing key
      presence/absence. It can be set explicitly using AmqpRequestOptions. Default value 'fanout' is used if exchange

From 6e66385cfbd48e36aeea72f586de6cb6fc5b874b Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 10 Feb 2017 18:02:42 +0200
Subject: [PATCH 183/226] [maven-release-plugin] prepare release msb-java-1.6.1

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 7 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index b61d039f..39cedb6a 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.1-SNAPSHOT
+        1.6.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.1
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index dad97e98..2eb77262 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.1-SNAPSHOT
+        1.6.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.1
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index f670a88c..ea480428 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.1-SNAPSHOT
+        1.6.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.1
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index fee0f2b4..e8642b5d 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.1-SNAPSHOT
+        1.6.1
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.1
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index ce2401e0..5cc69d1f 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.1-SNAPSHOT
+        1.6.1
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.1
     
 
     
diff --git a/pom.xml b/pom.xml
index ba124a17..00b07bda 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.1-SNAPSHOT
+    1.6.1
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.1
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index d0a0a048..0309c547 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.1-SNAPSHOT
+		1.6.1
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		HEAD
+		msb-java-1.6.1
 	
 
 	

From 557cc64a344a5ba8d1d32652221f5aae2a71f8ff Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 10 Feb 2017 18:02:51 +0200
Subject: [PATCH 184/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 7 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 39cedb6a..e4d37adf 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.1
+        1.6.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.1
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 2eb77262..6bbceebd 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.1
+        1.6.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.1
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index ea480428..30680364 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.1
+        1.6.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.1
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index e8642b5d..556edab7 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.1
+        1.6.2-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.1
+        HEAD
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 5cc69d1f..3606b8f5 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.1
+        1.6.2-SNAPSHOT
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.1
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index 00b07bda..9c2918d1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.1
+    1.6.2-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.1
+        HEAD
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index 0309c547..f5fb5a6e 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.1
+		1.6.2-SNAPSHOT
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		msb-java-1.6.1
+		HEAD
 	
 
 	

From 0acda896fbf5b71faf9bd18180044eb657fc78bb Mon Sep 17 00:00:00 2001
From: alex 
Date: Tue, 14 Mar 2017 17:33:57 +0200
Subject: [PATCH 185/226] Removed single attempt   limit for manual retry

---
 .../acceptance/RequesterResponderTest.java    | 34 +++++++++++++++++--
 .../AcknowledgementHandlerImpl.java           | 19 +++++------
 ...va => AcknowledgementHandlerImplTest.java} | 11 ++++--
 release-notes.html                            |  4 +++
 4 files changed, 54 insertions(+), 14 deletions(-)
 rename core/src/test/java/io/github/tcdl/msb/acknowledge/{AcknowledgementHandlerTest.java => AcknowledgementHandlerImplTest.java} (96%)

diff --git a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java
index d05ce2d5..2cf96dca 100644
--- a/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java
+++ b/acceptance/src/test/java/io/github/tcdl/msb/acceptance/RequesterResponderTest.java
@@ -92,7 +92,35 @@ public void tagsAreSentWithTheMessage() throws Exception {
     }
 
     @Test
-    public void singleRedeliveryOnRetry() throws Exception {
+    public void manualRetry() throws Exception {
+
+        MsbContext requesterContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+        MsbContext responderContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
+
+        int expectedDeliveryCount = 5;
+
+        AtomicInteger deliveryCount = new AtomicInteger(0);
+        responderContext.getObjectFactory().createResponderServer(NAMESPACE, ResponderOptions.DEFAULTS,
+                (request, ctx) -> {
+                    deliveryCount.incrementAndGet();
+                    if(deliveryCount.get() < expectedDeliveryCount) {
+                        ctx.getAcknowledgementHandler().retryMessage();
+                    } else {
+                        ctx.getAcknowledgementHandler().rejectMessage();
+                    }
+                },
+                String.class)
+                .listen();
+
+        requesterContext.getObjectFactory().createRequesterForFireAndForget(NAMESPACE).publish(REQUEST_BODY);
+
+        //wait a bit
+        TimeUnit.SECONDS.sleep(1);
+        assertEquals("Incorrect delivery count", expectedDeliveryCount, deliveryCount.get());
+    }
+
+    @Test
+    public void singleAutomaticRetryOnErrorHandlerFailure() throws Exception {
 
         MsbContext requesterContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
         MsbContext responderContext = helper.initDistinctContext(MsbTestHelper.temporaryInfrastructure(config));
@@ -103,7 +131,9 @@ public void singleRedeliveryOnRetry() throws Exception {
         responderContext.getObjectFactory().createResponderServer(NAMESPACE, ResponderOptions.DEFAULTS,
                 (request, ctx) -> {
                     deliveryCount.incrementAndGet();
-                    ctx.getAcknowledgementHandler().retryMessage();
+                    throw new RuntimeException("Trigger onError handler retry");
+                },(exception, originalMessage) -> {
+                    throw new RuntimeException("Trigger automatic retry");
                 },
                 String.class)
                 .listen();
diff --git a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java
index 678a13dc..1e23eea2 100644
--- a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java
@@ -26,7 +26,6 @@ public class AcknowledgementHandlerImpl implements AcknowledgementHandlerInterna
 
     public AcknowledgementHandlerImpl(AcknowledgementAdapter acknowledgementAdapter,
                                       boolean isMessageRedelivered, String messageTextIdentifier) {
-        super();
         this.acknowledgementAdapter = acknowledgementAdapter;
         this.isMessageRedelivered = isMessageRedelivered;
         this.messageTextIdentifier = messageTextIdentifier;
@@ -51,13 +50,8 @@ public void confirmMessage() {
     @Override
     public void retryMessage() {
         executeAck("requeue", () -> {
-            if(!isMessageRedelivered) {
-                acknowledgementAdapter.retry();
-                LOG.debug("[{}] A message was rejected with requeue", messageTextIdentifier);
-            } else {
-                acknowledgementAdapter.reject();
-                LOG.warn("[{}] Can't requeue message because it already was redelivered once, discarding it instead", messageTextIdentifier);
-            }
+            acknowledgementAdapter.retry();
+            LOG.debug("[{}] A message was rejected with requeue", messageTextIdentifier);
         });
     }
 
@@ -97,8 +91,13 @@ public void autoReject() {
 
     public void autoRetry() {
         executeAutoAck(() -> {
-            retryMessage();
-            LOG.debug("[{}] A message was automatically rejected (with a requeue attempt) due to error during message processing", messageTextIdentifier);
+            if (!isMessageRedelivered) {
+                retryMessage();
+                LOG.debug("[{}] A message was rejected with requeue", messageTextIdentifier);
+            } else {
+                rejectMessage();
+                LOG.warn("[{}] Can't requeue message because it already was redelivered once, discarding it instead", messageTextIdentifier);
+            }
         });
     }
 
diff --git a/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerTest.java b/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java
similarity index 96%
rename from core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerTest.java
rename to core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java
index 03166376..ed64971c 100644
--- a/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java
@@ -11,7 +11,7 @@
 
 
 @RunWith(MockitoJUnitRunner.class)
-public class AcknowledgementHandlerTest {
+public class AcknowledgementHandlerImplTest {
 
     private AcknowledgementHandlerImpl handler;
 
@@ -79,10 +79,17 @@ public void testRedeliveredMessageRejected() throws Exception {
     @Test
     public void testRedeliveredMessageRejectedInsteadOfRetry() throws Exception {
         handler = getHandler(true);
-        handler.retryMessage();
+        handler.autoRetry();
         verifySingleReject();
     }
 
+    @Test
+    public void testManuallyRetriedMessageNotRejected() throws Exception {
+        handler = getHandler(true);
+        handler.retryMessage();
+        verifySingleRetry();
+    }
+
     @Test
     public void testRedeliveredMessageConfirmed() throws Exception {
         handler = getHandler(true);
diff --git a/release-notes.html b/release-notes.html
index 2b517b78..42e7bf39 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -9,6 +9,10 @@ 

Welcome to MSB-Java version 1.6.1

February 2, 2017

+Changes in MSB-Java version 1.6.2:
+   - Manual message retry is not limited to a single attempt any more. It is completely up to client code to decide when
+     message should not be retried any more.
+
 Features of MSB-Java version 1.6.1:
    - Improved message processing during context shutdown;
    - Improved AMQP exception handling and channel automatic recovery;

From 1ff3836a5b712d0a3a592bc8b31f7608bd478be2 Mon Sep 17 00:00:00 2001
From: rdro-tc 
Date: Tue, 20 Jun 2017 13:30:51 +0300
Subject: [PATCH 186/226] CLI(restored) tool with topics support

---
 cli/README.md                                 |  31 +++++
 cli/pom.xml                                   |  63 +++++++++
 .../tcdl/msb/cli/CliMessageHandler.java       |  69 ++++++++++
 .../tcdl/msb/cli/CliMessageSubscriber.java    |  41 ++++++
 .../java/io/github/tcdl/msb/cli/CliTool.java  | 123 ++++++++++++++++++
 .../io/github/tcdl/msb/cli/MsbExchange.java   |  25 ++++
 cli/src/main/resources/application.conf       |  30 +++++
 cli/src/main/resources/logback.xml            |  14 ++
 .../tcdl/msb/cli/CliMessageHandlerTest.java   |  75 +++++++++++
 .../msb/cli/CliMessageSubscriberTest.java     |  63 +++++++++
 .../io/github/tcdl/msb/cli/CliToolTest.java   | 103 +++++++++++++++
 pom.xml                                       |   1 +
 12 files changed, 638 insertions(+)
 create mode 100644 cli/README.md
 create mode 100644 cli/pom.xml
 create mode 100644 cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java
 create mode 100644 cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java
 create mode 100644 cli/src/main/java/io/github/tcdl/msb/cli/CliTool.java
 create mode 100644 cli/src/main/java/io/github/tcdl/msb/cli/MsbExchange.java
 create mode 100644 cli/src/main/resources/application.conf
 create mode 100644 cli/src/main/resources/logback.xml
 create mode 100644 cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java
 create mode 100644 cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java
 create mode 100644 cli/src/test/java/io/github/tcdl/msb/cli/CliToolTest.java

diff --git a/cli/README.md b/cli/README.md
new file mode 100644
index 00000000..d2bab1ef
--- /dev/null
+++ b/cli/README.md
@@ -0,0 +1,31 @@
+# MSB-Java CLI
+
+Microservice bus - Java CLI 
+
+## Build:
+```
+mvn clean install
+```
+## Run:
+
+without options to show help:
+```
+java -jar msb-java-cli-*.jar  
+```
+with topic:
+```
+java -jar msb-java-cli-*.jar --topic pingpong:namespace
+```
+with external config:
+```
+java -Dconfig.file=./application.conf -jar msb-java-cli-*.jar --topic pingpong:namespace
+```
+## Usage
+
+Listens to a topic on the bus and prints JSON to stdout. By default it will also listen for response topics detected on messages, and JSON is pretty-printed. For [Newline-delimited JSON](http://en.wikipedia.org/wiki/Line_Delimited_JSON) compatibility, specify `-p false`.
+
+Options:
+- **--topic** or **-t** (By adding '.fanout' or '.topic' to topic name you can specify a type of the exchange, Default:'.fanout', Example: --topic pingpong:namespace.fanout)
+- **--follow** or **-f** listen for following topics, empty to disable (Default: response, ack)
+- **--pretty** or **-p** set to false to use as a newline-delimited json stream, (Default: true)
+- **-Dconfig.file=** specify external config file if needed
diff --git a/cli/pom.xml b/cli/pom.xml
new file mode 100644
index 00000000..241d4ca3
--- /dev/null
+++ b/cli/pom.xml
@@ -0,0 +1,63 @@
+
+
+    
+        io.github.tcdl.msb
+        msb-java
+        1.6.2-SNAPSHOT
+        ../pom.xml
+    
+    4.0.0
+    msb-java-cli
+    msb java cli
+    msb java cli
+    jar
+    
+        scm:git:https://github.com/tcdl/msb-java.git
+        scm:git:git@github.com:tcdl/msb-java.git
+        https://github.com/tcdl/msb-java
+        HEAD
+    
+    
+        tcdl
+        https://github.com/tcdl
+    
+    
+        
+            io.github.tcdl.msb
+            msb-java-core
+        
+        
+            io.github.tcdl.msb
+            msb-java-amqp
+        
+        
+            ch.qos.logback
+            logback-classic
+            runtime
+        
+    
+    
+        
+            
+                org.apache.maven.plugins
+                maven-shade-plugin
+                2.3
+                
+                    
+                        package
+                        
+                            shade
+                        
+                        
+                            
+                                
+                                    io.github.tcdl.msb.cli.CliTool
+                                
+                            
+                        
+                    
+                
+            
+        
+    
+
diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java
new file mode 100644
index 00000000..b497bc06
--- /dev/null
+++ b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageHandler.java
@@ -0,0 +1,69 @@
+package io.github.tcdl.msb.cli;
+
+import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.api.ExchangeType;
+import io.github.tcdl.msb.api.exception.JsonConversionException;
+
+import java.io.IOException;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+/**
+ * This handler dumps messages from the given topics.
+ *
+ * Additionally it examines the content of messages, looks for any other topics mentioned there (for example response topics) and subscribes to them as well.
+ */
+class CliMessageHandler implements ConsumerAdapter.RawMessageHandler {
+    private CliMessageSubscriber subscriber;
+    private boolean prettyOutput;
+    private List follow;
+
+    public CliMessageHandler(CliMessageSubscriber subscriber, List follow, boolean prettyOutput) {
+        this.subscriber = subscriber;
+        this.follow = follow;
+        this.prettyOutput = prettyOutput;
+    }
+
+    /**
+     * @throws JsonConversionException if some problems during parsing JSON
+     */
+    @Override
+    public void onMessage(String jsonMessage, AcknowledgementHandlerInternal handler) {
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+
+        if (prettyOutput) {
+            objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
+        }
+
+        try {
+            JsonNode jsonMessageObject = objectMapper.readTree(jsonMessage);
+
+            // Subscribe to additional topics found in the message
+            if (jsonMessageObject.has("topics")
+                    && jsonMessageObject.get("topics").has("response")
+                    && jsonMessageObject.get("topics").get("response").isTextual()
+                    && follow.contains("response")) {
+
+                String responseTopicName = jsonMessageObject.get("topics").get("response").asText();
+
+                try {
+                    subscriber.subscribe(responseTopicName, ExchangeType.FANOUT, this);
+                } catch (Exception e) {
+                    // Just ignore the exception
+                }
+            }
+
+            // Reformat json message in pretty way
+            Object tmpObject = objectMapper.readValue(jsonMessage, Object.class);
+            System.out.println(objectMapper.writeValueAsString(tmpObject));
+        } catch (IOException e) {
+            throw new JsonConversionException("Unable to process JSON", e);
+        }
+    }
+}
diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java
new file mode 100644
index 00000000..f023ebb3
--- /dev/null
+++ b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java
@@ -0,0 +1,41 @@
+package io.github.tcdl.msb.cli;
+
+import com.google.common.collect.Sets;
+import io.github.tcdl.msb.adapters.AdapterFactory;
+import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.api.AmqpResponderOptions;
+import io.github.tcdl.msb.api.ExchangeType;
+import io.github.tcdl.msb.api.ResponderOptions;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * {@link CliMessageSubscriber} uses raw {@link ConsumerAdapter} (that doesn't validate or parse incoming messages)
+ */
+public class CliMessageSubscriber {
+    private AdapterFactory adapterFactory;
+    private final Set registeredTopics = new HashSet<>();
+
+    public CliMessageSubscriber(AdapterFactory adapterFactory) {
+        this.adapterFactory = adapterFactory;
+    }
+
+    /**
+     * Subscribes the given handler to the given topic
+     */
+    public void subscribe(String topicName, ExchangeType exchangeType, CliMessageHandler handler) {
+        synchronized (registeredTopics) {
+            if (!registeredTopics.contains(topicName)) {
+                ResponderOptions responderOptions = new AmqpResponderOptions.Builder()
+                        .withExchangeType(exchangeType)
+                        .withBindingKeys(Sets.newHashSet("*"))
+                        .build();
+                ConsumerAdapter adapter = adapterFactory.createConsumerAdapter(topicName, responderOptions, false);
+
+                adapter.subscribe(handler);
+                registeredTopics.add(topicName);
+            }
+        }
+    }
+}
diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliTool.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliTool.java
new file mode 100644
index 00000000..81a1dc79
--- /dev/null
+++ b/cli/src/main/java/io/github/tcdl/msb/cli/CliTool.java
@@ -0,0 +1,123 @@
+package io.github.tcdl.msb.cli;
+
+import com.typesafe.config.ConfigFactory;
+import io.github.tcdl.msb.adapters.AdapterFactoryLoader;
+import io.github.tcdl.msb.api.ExchangeType;
+import io.github.tcdl.msb.config.MsbConfig;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * This tool allows to wiretap the given topics and log the messages from them. See {@link #printUsage()} for parameters supported.
+ */
+public class CliTool {
+
+    private static String FANOUT_EXCHANGE_PREFIX = ".fanout";
+    private static String TOPIC_EXCHANGE_PREFIX = ".topic";
+
+    public static void main(String[] args) {
+        MsbConfig msbConfig = new MsbConfig(ConfigFactory.load());
+        AdapterFactoryLoader adapterFactoryLoader = new AdapterFactoryLoader(msbConfig);
+
+        // Parse command line arguments
+        List topics = getTopics(args);
+        if (topics == null || topics.isEmpty()) {
+            // at least one topic is required
+            printUsage();
+            return;
+        }
+        boolean prettyOutput = getPrettyOutput(args);
+        List follow = getFollow(args);
+
+        List exchanges = topics.stream()
+                .map(CliTool::parseExchange)
+                .collect(Collectors.toList());
+
+        CliMessageSubscriber subscriptionManager = new CliMessageSubscriber(adapterFactoryLoader.getAdapterFactory());
+        subscribe(subscriptionManager, exchanges, follow, prettyOutput);
+    }
+
+    private static void printUsage() {
+        System.out.println("Usage: CliTool <--topic|-t topic1,topic2> [--pretty true|false] [--follow response]\n"
+                + "--topic (required) specifies topic(s) to listen to. By adding '.fanout' or '.topic' you can specify a type of the exchange\n"
+                + "--pretty (defaults to 'true') display formatted or not formatted messages\n"
+                + "--follow (defaults to 'response') allows to inspect incoming messages and subscribe to response topics automatically");
+    }
+
+    static void subscribe(CliMessageSubscriber subscriptionManager, List exchanges, List follow, boolean prettyOutput) {
+        for (MsbExchange exchange : exchanges) {
+            subscriptionManager.subscribe(exchange.getNamespace(), exchange.getType(), new CliMessageHandler(subscriptionManager, follow, prettyOutput));
+        }
+    }
+
+    static MsbExchange parseExchange(String topic) {
+        String namespace;
+        ExchangeType type;
+
+        if (topic.endsWith(FANOUT_EXCHANGE_PREFIX)) {
+            type = ExchangeType.FANOUT;
+            namespace = topic.substring(0, topic.length() - FANOUT_EXCHANGE_PREFIX.length());
+        } else if (topic.endsWith(TOPIC_EXCHANGE_PREFIX)) {
+            type = ExchangeType.TOPIC;
+            namespace = topic.substring(0, topic.length() - TOPIC_EXCHANGE_PREFIX.length());
+        } else {
+            type = ExchangeType.FANOUT;
+            namespace = topic;
+        }
+
+        return new MsbExchange(namespace, type);
+    }
+
+    static List getTopics(String[] args) {
+        return getOptionAsList(args, "--topic", "-t");
+    }
+
+    static List getFollow(String[] args) {
+        List follow = getOptionAsList(args, "--follow", "-f");
+        if (follow == null || follow.isEmpty()) {
+            follow = Collections.singletonList("response");
+        }
+        return follow;
+    }
+
+    static boolean getPrettyOutput(String[] args) {
+        return getOptionAsBoolean(args, "--pretty", "-p");
+    }
+
+    static boolean getOptionAsBoolean(String[] args, String optionName, String alias) {
+        for (int i = 0; i < args.length; i++) {
+            String arg = args[i];
+
+            if (arg.equals(optionName) || arg.equals(alias)) {
+                if (i + 1 < args.length) {
+                    String optionValue = args[i + 1];
+
+                    return optionValue.equals("true");
+                }
+            }
+        }
+
+        return true;
+    }
+
+    static List getOptionAsList(String[] args, String optionName, String alias) {
+        for (int i = 0; i < args.length; i++) {
+            String arg = args[i];
+
+            if (arg.equals(optionName) || arg.equals(alias)) {
+                if (i + 1 < args.length) {
+                    String optionValue = args[i + 1];
+
+                    return Arrays.asList(optionValue.split(","));
+                }
+            }
+        }
+
+        return null;
+    }
+    
+ }
diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/MsbExchange.java b/cli/src/main/java/io/github/tcdl/msb/cli/MsbExchange.java
new file mode 100644
index 00000000..c1ac9ea1
--- /dev/null
+++ b/cli/src/main/java/io/github/tcdl/msb/cli/MsbExchange.java
@@ -0,0 +1,25 @@
+package io.github.tcdl.msb.cli;
+
+import io.github.tcdl.msb.api.ExchangeType;
+
+/**
+ * Represents msb namespace with specific exchange type.
+ */
+public class MsbExchange {
+
+    private String namespace;
+    private ExchangeType type;
+
+    public MsbExchange(String namespace, ExchangeType type) {
+        this.namespace = namespace;
+        this.type = type;
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public ExchangeType getType() {
+        return type;
+    }
+}
diff --git a/cli/src/main/resources/application.conf b/cli/src/main/resources/application.conf
new file mode 100644
index 00000000..5e168911
--- /dev/null
+++ b/cli/src/main/resources/application.conf
@@ -0,0 +1,30 @@
+msbConfig {
+
+  # Service Details
+  serviceDetails = {
+     name = "msb_java_cli"
+     version = "1.0.1"
+     instanceId = "msbjcli-ed59-4a39-9f95-811c5fb6ab87"
+   }
+
+  brokerAdapterFactory = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory"
+
+  # Thread pool used for scheduling ack and response timeout tasks
+  timerThreadPoolSize: 2
+
+  threadingConfig = {
+    consumerThreadPoolSize = 5
+    # -1 means unlimited
+    consumerThreadPoolQueueCapacity = 20
+  }
+
+  # Broker Adapter Defaults
+  brokerConfig = {
+    host = "127.0.0.1"
+    port = "5672"
+    groupId = "cli-tool"
+    durable = false
+    prefetchCount = 0
+  }
+
+}
diff --git a/cli/src/main/resources/logback.xml b/cli/src/main/resources/logback.xml
new file mode 100644
index 00000000..6e35c0af
--- /dev/null
+++ b/cli/src/main/resources/logback.xml
@@ -0,0 +1,14 @@
+
+
+    
+        
+            UTF-8
+            %d{HH:mm:ss.SSS} %-5level %logger [%t] %c{1} - %m%n
+        
+    
+
+    
+        
+        
+    
+
\ No newline at end of file
diff --git a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java
new file mode 100644
index 00000000..c398d46d
--- /dev/null
+++ b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageHandlerTest.java
@@ -0,0 +1,75 @@
+package io.github.tcdl.msb.cli;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import java.util.Collections;
+
+import io.github.tcdl.msb.api.ExchangeType;
+import org.junit.Test;
+
+public class CliMessageHandlerTest {
+    @Test
+    public void testSubscriptionToResponseQueue() {
+        CliMessageSubscriber subscriber = mock(CliMessageSubscriber.class);
+
+        CliMessageHandler handler = new CliMessageHandler(subscriber, Collections.singletonList("response"), true);
+        handler.onMessage(
+                "{  \"topics\": {\n"
+                        + "    \"to\": \"search:parsers:facets:v1\",\n"
+                        + "    \"response\": \"search:parsers:facets:v1:response:3c3dec275b326c6500010843\"\n"
+                        + "  }}", null
+        );
+
+        verify(subscriber).subscribe("search:parsers:facets:v1:response:3c3dec275b326c6500010843", ExchangeType.FANOUT, handler);
+    }
+
+    @Test
+    public void testNoSubscriptionIfMissingResponseQueue() {
+        CliMessageSubscriber subscriber = mock(CliMessageSubscriber.class);
+
+        CliMessageHandler handler = new CliMessageHandler(subscriber, Collections.singletonList("response"), true);
+        handler.onMessage(
+                "{  \"topics\": {\n"
+                        + "    \"to\": \"search:parsers:facets:v1\"\n"
+                        + "  }}", null
+        );
+
+        verifyNoMoreInteractions(subscriber);
+    }
+
+    @Test
+    public void testNoSubscriptionIfNullResponseQueue() {
+        CliMessageSubscriber subscriber = mock(CliMessageSubscriber.class);
+
+        CliMessageHandler handler = new CliMessageHandler(subscriber, Collections.singletonList("response"), true);
+        handler.onMessage(
+                "{  \"topics\": {\n"
+                        + "    \"to\": \"search:parsers:facets:v1\",\n"
+                        + "    \"response\": null\n"
+                        + "  }}", null
+        );
+
+        verifyNoMoreInteractions(subscriber);
+    }
+
+    @Test
+    public void testSubscriptionNonExistingQueue() {
+        CliMessageSubscriber subscriber = mock(CliMessageSubscriber.class);
+        CliMessageHandler handler = new CliMessageHandler(subscriber, Collections.singletonList("response"), true);
+
+        doThrow(new RuntimeException()).when(subscriber).subscribe("non-existent-queue", ExchangeType.FANOUT, handler);
+
+        handler.onMessage(
+                "{  \"topics\": {\n"
+                        + "    \"to\": \"search:parsers:facets:v1\",\n"
+                        + "    \"response\": \"non-existent-queue\"\n"
+                        + "  }}", null
+        );
+
+        // The point of this test is to verify that no exception is thrown in such case
+        // that's why we don't have any explicit assert or verification here
+    }
+}
\ No newline at end of file
diff --git a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java
new file mode 100644
index 00000000..14461501
--- /dev/null
+++ b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java
@@ -0,0 +1,63 @@
+package io.github.tcdl.msb.cli;
+
+import io.github.tcdl.msb.adapters.AdapterFactory;
+import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.api.ExchangeType;
+import io.github.tcdl.msb.api.ResponderOptions;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class CliMessageSubscriberTest {
+
+    public static final String TOPIC_NAME = "topic1";
+
+    private AdapterFactory mockAdapterFactory;
+    private ConsumerAdapter mockConsumerAdapter;
+    private CliMessageHandler mockMessageHandler;
+
+    private CliMessageSubscriber subscriptionManager;
+
+    @Before
+    public void setUp() {
+        mockAdapterFactory = mock(AdapterFactory.class);
+        mockConsumerAdapter = mock(ConsumerAdapter.class);
+        when(mockAdapterFactory.createConsumerAdapter(eq(TOPIC_NAME), any(ResponderOptions.class),eq(false))).thenReturn(mockConsumerAdapter);
+
+        mockMessageHandler = mock(CliMessageHandler.class);
+
+        subscriptionManager = new CliMessageSubscriber(mockAdapterFactory);
+    }
+
+    @Test
+    public void testInitialSubscriptionForTopic() {
+        testInitialSubscription(TOPIC_NAME);
+    }
+
+    @Test
+    public void testDuplicateSubscription() {
+        testInitialSubscription(TOPIC_NAME);
+
+        // make another subscription to the same topic
+        subscriptionManager.subscribe(TOPIC_NAME, ExchangeType.FANOUT, mockMessageHandler);
+
+        // verify that nothing happens
+        verifyNoMoreInteractions(mockAdapterFactory);
+        verifyNoMoreInteractions(mockAdapterFactory);
+    }
+
+    private void testInitialSubscription(String topicName) {
+        // method under test
+        subscriptionManager.subscribe(topicName, ExchangeType.FANOUT, mockMessageHandler);
+
+        verify(mockAdapterFactory).createConsumerAdapter(eq(topicName), any(ResponderOptions.class),eq(false));
+        verify(mockConsumerAdapter).subscribe(mockMessageHandler);
+    }
+}
\ No newline at end of file
diff --git a/cli/src/test/java/io/github/tcdl/msb/cli/CliToolTest.java b/cli/src/test/java/io/github/tcdl/msb/cli/CliToolTest.java
new file mode 100644
index 00000000..918c63f9
--- /dev/null
+++ b/cli/src/test/java/io/github/tcdl/msb/cli/CliToolTest.java
@@ -0,0 +1,103 @@
+package io.github.tcdl.msb.cli;
+
+import io.github.tcdl.msb.api.ExchangeType;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class CliToolTest {
+    @Test
+    public void testStrToArr() {
+        String[] args = strToArr("1 2 3");
+        assertArrayEquals(new String[] { "1", "2", "3" }, args);
+
+        args = strToArr("--topic search:parsers:facets:v1");
+        assertArrayEquals(new String[] {"--topic", "search:parsers:facets:v1"}, args);
+    }
+
+    @Test
+    public void testGetOptionAsBoolean() {
+        boolean optionValue = CliTool.getOptionAsBoolean(strToArr("--pretty true"), "--pretty", "-p");
+        assertTrue(optionValue);
+
+        optionValue = CliTool.getOptionAsBoolean(strToArr("-p false"), "--pretty", "-p");
+        assertFalse(optionValue);
+
+        optionValue = CliTool.getOptionAsBoolean(strToArr("-p blah"), "--pretty", "-p");
+        assertFalse(optionValue);
+    }
+
+    @Test
+    public void testGetOptionAsListSingleValue() {
+        List optionValues = CliTool.getOptionAsList(strToArr("--topic search:parsers:facets:v1"), "--topic", "-t");
+        assertNotNull(optionValues);
+        assertEquals(1, optionValues.size());
+        assertTrue(optionValues.contains("search:parsers:facets:v1"));
+    }
+
+    @Test
+    public void testGetOptionAsListMultipleValues() {
+        List optionValues = CliTool.getOptionAsList(strToArr("--topic search:parsers:facets:v1,search:packages:v1"), "--topic", "-t");
+        assertNotNull(optionValues);
+        assertEquals(2, optionValues.size());
+        assertTrue(optionValues.contains("search:parsers:facets:v1"));
+        assertTrue(optionValues.contains("search:packages:v1"));
+    }
+
+    @Test
+    public void testGetOptionAsListNoValues() {
+        List optionValues = CliTool.getOptionAsList(strToArr("a b c"), "--topic", "-t");
+        assertNull(optionValues);
+    }
+
+    @Test
+    public void testGetFollowDefaultValue() {
+        List follow = CliTool.getFollow(strToArr("--topic search:parsers:facets:v1"));
+        assertNotNull(follow);
+        assertEquals(1, follow.size());
+        assertTrue(follow.contains("response"));
+    }
+
+    @Test
+    public void testGetTopicsDefaultValue() {
+        List topics = CliTool.getTopics(strToArr("--follow response,ack"));
+        assertNull(topics);
+    }
+
+    @Test
+    public void getPrettyOutputDefaultValue() {
+        boolean prettyOutput = CliTool.getPrettyOutput(strToArr("--follow response,ack"));
+        assertTrue(prettyOutput);
+    }
+
+    @Test
+    public void testSubscribe() {
+        CliMessageSubscriber subscriptionManager = mock(CliMessageSubscriber.class);
+        List topics = Arrays.asList(
+                new MsbExchange("topic1", ExchangeType.FANOUT),
+                new MsbExchange("topic2", ExchangeType.TOPIC));
+        List follow = Collections.singletonList("response");
+
+        CliTool.subscribe(subscriptionManager, topics, follow, false);
+
+        verify(subscriptionManager).subscribe(eq("topic1"), eq(ExchangeType.FANOUT), any(CliMessageHandler.class));
+        verify(subscriptionManager).subscribe(eq("topic2"), eq(ExchangeType.TOPIC), any(CliMessageHandler.class));
+    }
+
+    private String[] strToArr(String argString) {
+        return argString.split(" ");
+    }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 9c2918d1..18f1e77c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,7 @@
     
         core
         amqp
+        cli
         acceptance
         jmeter
         examples

From 386c4510ea0241dc4262d39841b97984c66f11f3 Mon Sep 17 00:00:00 2001
From: sergiimeleshko 
Date: Mon, 26 Jun 2017 15:27:03 +0300
Subject: [PATCH 187/226] [IN-2052] All logging of full message was replaced
 with separated message log with TRACE level.

---
 .../adapters/amqp/AmqpMessageConsumer.java    | 11 ++++----
 .../java/io/github/tcdl/msb/Consumer.java     | 25 +++++++++++++------
 .../java/io/github/tcdl/msb/Producer.java     |  2 +-
 .../github/tcdl/msb/collector/Collector.java  | 10 +++++---
 .../tcdl/msb/impl/NoopResponderImpl.java      |  6 +++--
 5 files changed, 34 insertions(+), 20 deletions(-)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
index 660bf2b1..51abeb42 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
@@ -43,15 +43,16 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp
 
             String bodyStr = new String(body, charset);
 
-            LOG.debug("[consumer tag: {}] Message consumed from broker: {}", consumerTag, bodyStr);
+            LOG.debug("[consumer tag: {}] Message consumed from broker.", consumerTag);
+            LOG.trace("Message: {}", bodyStr);
 
             try {
                 msgHandler.onMessage(bodyStr, ackHandler);
-                LOG.debug("[consumer tag: {}] Raw message has been handled: {}.",
-                        consumerTag, bodyStr);
+                LOG.debug("[consumer tag: {}] Raw message has been handled.", consumerTag);
+                LOG.trace("Message: {}", bodyStr);
             } catch (Exception e) {
-                LOG.error("[consumer tag: {}] Can't handle a raw message: {}.",
-                        consumerTag, bodyStr, e);
+                LOG.error("[consumer tag: {}] Can't handle a raw message.", consumerTag);
+                LOG.trace("Message: {}.", bodyStr);
                 throw e;
             }
         } catch (Exception e) {
diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java
index 320bcea4..56c4e713 100644
--- a/core/src/main/java/io/github/tcdl/msb/Consumer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java
@@ -99,14 +99,16 @@ public void end() {
      * @param jsonMessage message to process
      */
     protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerInternal acknowledgeHandler) {
-        LOG.debug("{} message received [{}]", loggingTag, jsonMessage);
+        LOG.debug("{} message received.", loggingTag);
+        LOG.trace("Message: {}", jsonMessage);
 
         Message message;
 
         try {
             message = parseMessage(jsonMessage);
         } catch (Exception e) {
-            LOG.error("{} Unable to process consumed message {}", loggingTag, jsonMessage, e);
+            LOG.error("{} ", loggingTag, e);
+            LOG.trace("Unable to process consumed message: {}", jsonMessage);
             acknowledgeHandler.autoReject();
             return;
         }
@@ -119,7 +121,8 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern
             }
 
             if (isMessageExpired(message)) {
-                LOG.warn("{} Expired message: {}", loggingTag, jsonMessage);
+                LOG.warn("{} Expired message.", loggingTag);
+                LOG.trace("Message: {}", jsonMessage);
                 acknowledgeHandler.autoReject();
                 return;
             }
@@ -133,11 +136,13 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern
                 }
                 messageHandlerInvoker.execute(messageHandler, message, acknowledgeHandler);
             } else {
-                LOG.warn("{} Cant't resolve message handler for a message: {}", loggingTag, jsonMessage);
+                LOG.warn("{} Can't resolve message handler.", loggingTag);
+                LOG.trace("Message: {}", jsonMessage);
                 acknowledgeHandler.autoReject();
             }
         } catch (Exception e) {
-            LOG.warn("{} Error while trying to handle a message: {}", loggingTag, jsonMessage, e);
+            LOG.warn("{} Error while trying to handle a message. ", loggingTag, e);
+            LOG.trace("Message: {}", jsonMessage);
             acknowledgeHandler.autoRetry();
             if(consumedMessagesAwareMessageHandler != null) {
                 consumedMessagesAwareMessageHandler.notifyConsumedMessageIsLost();
@@ -151,12 +156,16 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern
 
     private Message parseMessage(String jsonMessage) {
         if (msbConfig.getSchema() != null && !Utils.isServiceTopic(topic) && msbConfig.isValidateMessage()) {
-            LOG.debug("{} Validating schema for {}", loggingTag, jsonMessage);
+            LOG.debug("{} Validating schema.", loggingTag);
+            LOG.trace("Message: {}", jsonMessage);
             validator.validate(jsonMessage, msbConfig.getSchema());
         }
-        LOG.debug("{} Parsing message {}", loggingTag, jsonMessage);
+        LOG.debug("{} Parsing message.", loggingTag);
+        LOG.trace("Message: {}", jsonMessage);
+
         Message result = Utils.fromJson(jsonMessage, Message.class, messageMapper);
-        LOG.debug("{} Message has been successfully parsed {}", loggingTag, jsonMessage);
+        LOG.debug("{} Message has been successfully parsed.", loggingTag);
+        LOG.trace("Message: {}", jsonMessage);
         return result;
     }
 
diff --git a/core/src/main/java/io/github/tcdl/msb/Producer.java b/core/src/main/java/io/github/tcdl/msb/Producer.java
index 43cc6c0d..e8518bba 100644
--- a/core/src/main/java/io/github/tcdl/msb/Producer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Producer.java
@@ -36,7 +36,7 @@ public void publish(Message message) {
         String routingKey = message.getTopics().getRoutingKey();
         try {
             String jsonMessage = Utils.toJson(message, messageMapper);
-            LOG.debug("Publishing message to adapter : {}", jsonMessage);
+            LOG.trace("Publishing message to adapter : {}", jsonMessage);
             rawAdapter.publish(jsonMessage, routingKey != null ? routingKey : StringUtils.EMPTY);
         } catch (ChannelException | JsonConversionException e) {
             LOG.error("Exception while message publish to adapter", e);
diff --git a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
index 8654985d..64fbf1e8 100644
--- a/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
+++ b/core/src/main/java/io/github/tcdl/msb/collector/Collector.java
@@ -179,16 +179,18 @@ public void listenForResponses() {
 
     @Override
     public void handleMessage(Message incomingMessage, AcknowledgementHandler acknowledgeHandler) {
-        LOG.debug("[correlation ids: {}-{}] Received {}",
-                requestMessage.getCorrelationId(), incomingMessage.getCorrelationId(), incomingMessage);
+        LOG.debug("[correlation ids: {}-{}] Received.",
+                requestMessage.getCorrelationId(), incomingMessage.getCorrelationId());
+        LOG.trace("Message: {}", incomingMessage);
 
         JsonNode rawPayload = incomingMessage.getRawPayload();
         MessageContext messageContext = createMessageContext(acknowledgeHandler, incomingMessage);
         boolean isWithPayload = Utils.isPayloadPresent(rawPayload);
 
         if (isWithPayload) {
-            LOG.debug("[correlation ids: {}-{}] Received Payload {}",
-                    requestMessage.getCorrelationId(), incomingMessage.getCorrelationId(), rawPayload);
+            LOG.debug("[correlation ids: {}-{}] Received Payload.",
+                    requestMessage.getCorrelationId(), incomingMessage.getCorrelationId());
+            LOG.trace("Payload: {}", rawPayload);
             payloadMessages.add(incomingMessage);
             try {
                 onRawResponse.ifPresent(handler -> handler.accept(incomingMessage, messageContext));
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java
index 424209b1..3be301f3 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java
@@ -21,13 +21,15 @@ public NoopResponderImpl(Message originalMessage) {
     /** {@inheritDoc} */
     @Override
     public void sendAck(Integer timeoutMs, Integer responsesRemaining) {
-        LOG.error("Cannot send ack because response topic is unknown. Incoming message: {}", originalMessage);
+        LOG.error("Cannot send ack because response topic is unknown.");
+        LOG.trace("Incoming message: {}", originalMessage);
     }
 
     /** {@inheritDoc} */
     @Override
     public void send(Object responsePayload) {
-        LOG.error("Cannot send response because response topic is unknown. Incoming message: {}", originalMessage);
+        LOG.error("Cannot send response because response topic is unknown.");
+        LOG.trace("Incoming message: {}", originalMessage);
     }
 
 }

From 8e26205ae825f0dd30392c9010a3cb16c78b4efe Mon Sep 17 00:00:00 2001
From: sergiimeleshko 
Date: Mon, 26 Jun 2017 17:43:04 +0300
Subject: [PATCH 188/226] [IN-2052] added missed exception to the log message

---
 .../io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
index 51abeb42..8b10b8fb 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
@@ -51,7 +51,7 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp
                 LOG.debug("[consumer tag: {}] Raw message has been handled.", consumerTag);
                 LOG.trace("Message: {}", bodyStr);
             } catch (Exception e) {
-                LOG.error("[consumer tag: {}] Can't handle a raw message.", consumerTag);
+                LOG.error("[consumer tag: {}] Can't handle a raw message.", consumerTag, e);
                 LOG.trace("Message: {}.", bodyStr);
                 throw e;
             }

From 87471c5ac02ccce834f0a3b3bb1043c30957baaf Mon Sep 17 00:00:00 2001
From: sergiimeleshko 
Date: Tue, 27 Jun 2017 12:54:23 +0300
Subject: [PATCH 189/226] [IN-2052] added message information to log where
 possible

---
 .../adapters/amqp/AmqpMessageConsumer.java    |  2 +-
 .../amqp/AmqpMessageConsumerTest.java         | 32 ++++++++-----------
 .../java/io/github/tcdl/msb/Consumer.java     |  8 +++--
 .../tcdl/msb/impl/NoopResponderImpl.java      |  7 ++--
 4 files changed, 24 insertions(+), 25 deletions(-)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
index 8b10b8fb..1346e184 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumer.java
@@ -52,7 +52,7 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp
                 LOG.trace("Message: {}", bodyStr);
             } catch (Exception e) {
                 LOG.error("[consumer tag: {}] Can't handle a raw message.", consumerTag, e);
-                LOG.trace("Message: {}.", bodyStr);
+                LOG.trace("Message: {}", bodyStr);
                 throw e;
             }
         } catch (Exception e) {
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java
index 8bcc1baf..aeca9ee5 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpMessageConsumerTest.java
@@ -1,31 +1,27 @@
 package io.github.tcdl.msb.adapters.amqp;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
-
-import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Envelope;
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl;
+import io.github.tcdl.msb.adapters.ConsumerAdapter;
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
-
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.RejectedExecutionException;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Envelope;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.concurrent.RejectedExecutionException;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.*;
+
 @RunWith(MockitoJUnitRunner.class)
 public class AmqpMessageConsumerTest {
 
diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java
index 56c4e713..3184e5bd 100644
--- a/core/src/main/java/io/github/tcdl/msb/Consumer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java
@@ -121,7 +121,7 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern
             }
 
             if (isMessageExpired(message)) {
-                LOG.warn("{} Expired message.", loggingTag);
+                LOG.warn("[correlation id: {}, message id: {}] {} Expired message. ", message.getCorrelationId(), message.getId(), loggingTag);
                 LOG.trace("Message: {}", jsonMessage);
                 acknowledgeHandler.autoReject();
                 return;
@@ -141,7 +141,8 @@ protected void handleRawMessage(String jsonMessage, AcknowledgementHandlerIntern
                 acknowledgeHandler.autoReject();
             }
         } catch (Exception e) {
-            LOG.warn("{} Error while trying to handle a message. ", loggingTag, e);
+            LOG.warn("[correlation id: {}, message id: {}] {} Error while trying to handle a message. ",
+                    message.getCorrelationId(), message.getId(), loggingTag, e);
             LOG.trace("Message: {}", jsonMessage);
             acknowledgeHandler.autoRetry();
             if(consumedMessagesAwareMessageHandler != null) {
@@ -164,7 +165,8 @@ private Message parseMessage(String jsonMessage) {
         LOG.trace("Message: {}", jsonMessage);
 
         Message result = Utils.fromJson(jsonMessage, Message.class, messageMapper);
-        LOG.debug("{} Message has been successfully parsed.", loggingTag);
+        LOG.debug("[correlation id: {}, message id: {}] {} Message has been successfully parsed.",
+                result.getCorrelationId(), result.getId(), loggingTag);
         LOG.trace("Message: {}", jsonMessage);
         return result;
     }
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java
index 3be301f3..8eda64bc 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/NoopResponderImpl.java
@@ -2,7 +2,6 @@
 
 import io.github.tcdl.msb.api.Responder;
 import io.github.tcdl.msb.api.message.Message;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -21,14 +20,16 @@ public NoopResponderImpl(Message originalMessage) {
     /** {@inheritDoc} */
     @Override
     public void sendAck(Integer timeoutMs, Integer responsesRemaining) {
-        LOG.error("Cannot send ack because response topic is unknown.");
+        LOG.error("[correlation id: {}, message id: {}] Cannot send ack because response topic is unknown.",
+                originalMessage.getCorrelationId(), originalMessage.getId());
         LOG.trace("Incoming message: {}", originalMessage);
     }
 
     /** {@inheritDoc} */
     @Override
     public void send(Object responsePayload) {
-        LOG.error("Cannot send response because response topic is unknown.");
+        LOG.error("[correlation id: {}, message id: {}] Cannot send response because response topic is unknown.",
+                originalMessage.getCorrelationId(), originalMessage.getId());
         LOG.trace("Incoming message: {}", originalMessage);
     }
 

From 7e86bd2a0bde8f6bd6218d0e1fd78ada93910602 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Mon, 3 Jul 2017 13:45:02 +0300
Subject: [PATCH 190/226] 1.6.2 release preparation

---
 release-notes.html | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/release-notes.html b/release-notes.html
index 42e7bf39..655a563e 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -4,14 +4,16 @@
 
 
 
-

Welcome to MSB-Java version 1.6.1

+

Welcome to MSB-Java version 1.6.2

-

February 2, 2017

+

July 3, 2017

 Changes in MSB-Java version 1.6.2:
-   - Manual message retry is not limited to a single attempt any more. It is completely up to client code to decide when
-     message should not be retried any more.
+   - Manual message retry is not limited to a single attempt anymore. It is completely up to client code to decide 
+     when message should not be retried anymore;
+   - Restored CLI tool. Supported 'fanout' and 'topic' exchange types;
+   - Full message logging was replaced with separated message log with TRACE level;
 
 Features of MSB-Java version 1.6.1:
    - Improved message processing during context shutdown;

From e8359b5b80de84f70783b7eafcb577be8acf8221 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Mon, 3 Jul 2017 15:22:39 +0300
Subject: [PATCH 191/226] [maven-release-plugin] prepare release msb-java-1.6.2

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 cli/pom.xml                 | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index e4d37adf..d183ddf9 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2-SNAPSHOT
+        1.6.2
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.2
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 6bbceebd..cbbac326 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2-SNAPSHOT
+        1.6.2
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.2
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 241d4ca3..1a430cfe 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2-SNAPSHOT
+        1.6.2
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.2
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 30680364..103cedcb 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2-SNAPSHOT
+        1.6.2
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.2
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 556edab7..ba4ce1f9 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2-SNAPSHOT
+        1.6.2
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.2
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 3606b8f5..802e6625 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2-SNAPSHOT
+        1.6.2
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.2
     
 
     
diff --git a/pom.xml b/pom.xml
index 18f1e77c..b759353e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.2-SNAPSHOT
+    1.6.2
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.2
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index f5fb5a6e..8e40b1ad 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.2-SNAPSHOT
+		1.6.2
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		HEAD
+		msb-java-1.6.2
 	
 
 	

From 55939eadd00d876ad958912cadf6742e2e4dd881 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Mon, 3 Jul 2017 15:22:47 +0300
Subject: [PATCH 192/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 cli/pom.xml                 | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index d183ddf9..b13a6946 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2
+        1.6.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.2
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index cbbac326..1f008b0e 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2
+        1.6.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.2
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 1a430cfe..5b9611ed 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2
+        1.6.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.2
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 103cedcb..656bcffa 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2
+        1.6.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.2
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index ba4ce1f9..45549303 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2
+        1.6.3-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.2
+        HEAD
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 802e6625..2b7cb860 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.2
+        1.6.3-SNAPSHOT
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.2
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index b759353e..b4748853 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.2
+    1.6.3-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.2
+        HEAD
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index 8e40b1ad..73dd6daa 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.2
+		1.6.3-SNAPSHOT
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		msb-java-1.6.2
+		HEAD
 	
 
 	

From d497e7146f0412065f3aee346444ef6df6a5c6e0 Mon Sep 17 00:00:00 2001
From: sergiimeleshko 
Date: Fri, 25 Aug 2017 18:04:48 +0300
Subject: [PATCH 193/226] [IN-2503] added separate log records for code when
 message body was put to exception

---
 .../tcdl/msb/adapters/amqp/AmqpProducerAdapter.java      | 8 +++++++-
 .../java/io/github/tcdl/msb/support/JsonValidator.java   | 9 +++++++--
 2 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
index 10ecb2ee..45a22f51 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
@@ -7,10 +7,14 @@
 import io.github.tcdl.msb.config.amqp.AmqpBrokerConfig;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.nio.charset.Charset;
 
 public class AmqpProducerAdapter implements ProducerAdapter {
+    private static final Logger LOG = LoggerFactory.getLogger(AmqpProducerAdapter.class);
+    private static final String ERROR_MESSAGE_TEMPLATE = "Failed to publish message into exchange '%s' with routing key '%s'";
 
     final String exchangeName;
     final AmqpBrokerConfig amqpBrokerConfig;
@@ -49,7 +53,9 @@ public void publish(String jsonMessage, String routingKey) {
         try {
             channel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_BASIC, jsonMessage.getBytes(charset));
         } catch (Exception e) {
-            throw new ChannelException(String.format("Failed to publish message '%s' into exchange '%s' with routing key '%s'", jsonMessage, exchangeName, routingKey), e);
+            LOG.debug(ERROR_MESSAGE_TEMPLATE, exchangeName, routingKey);
+            LOG.trace("Message: {}", jsonMessage);
+            throw new ChannelException(String.format(ERROR_MESSAGE_TEMPLATE, exchangeName, routingKey), e);
         }
     }
 }
diff --git a/core/src/main/java/io/github/tcdl/msb/support/JsonValidator.java b/core/src/main/java/io/github/tcdl/msb/support/JsonValidator.java
index ffc99b4e..af78f1d2 100644
--- a/core/src/main/java/io/github/tcdl/msb/support/JsonValidator.java
+++ b/core/src/main/java/io/github/tcdl/msb/support/JsonValidator.java
@@ -8,6 +8,8 @@
 import com.github.fge.jsonschema.main.JsonSchemaFactory;
 import io.github.tcdl.msb.api.exception.JsonSchemaValidationException;
 import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.StringReader;
@@ -18,7 +20,8 @@
  * Validates JSON against JSON Schema
  */
 public class JsonValidator {
-
+    private static final Logger LOG = LoggerFactory.getLogger(JsonValidator.class);
+    private static final String ERROR_MESSAGE_TEMPLATE = "Error while validating message using schema '%s'";
     private Map schemaCache = new ConcurrentHashMap<>();
     private JsonReader jsonReader;
 
@@ -58,7 +61,9 @@ public void validate(String json, String schema) {
             }
 
         } catch (IOException | ProcessingException e) {
-            throw new JsonSchemaValidationException(String.format("Error while validating message '%s' using schema '%s'", json, schema), e);
+            LOG.error(ERROR_MESSAGE_TEMPLATE, schema);
+            LOG.trace("Message: {}", json);
+            throw new JsonSchemaValidationException(String.format(ERROR_MESSAGE_TEMPLATE, schema), e);
         }
     }
 

From 79dc67801ae2df1ab18ae8cd2daa468c534a5478 Mon Sep 17 00:00:00 2001
From: sergiimeleshko 
Date: Mon, 28 Aug 2017 18:00:33 +0300
Subject: [PATCH 194/226] [IN-2503] change log level to error while catching
 exception

---
 .../io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
index 45a22f51..10f68828 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpProducerAdapter.java
@@ -53,7 +53,7 @@ public void publish(String jsonMessage, String routingKey) {
         try {
             channel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_BASIC, jsonMessage.getBytes(charset));
         } catch (Exception e) {
-            LOG.debug(ERROR_MESSAGE_TEMPLATE, exchangeName, routingKey);
+            LOG.error(ERROR_MESSAGE_TEMPLATE, exchangeName, routingKey);
             LOG.trace("Message: {}", jsonMessage);
             throw new ChannelException(String.format(ERROR_MESSAGE_TEMPLATE, exchangeName, routingKey), e);
         }

From 839778fdee56fd58158ce4525e25055d60d992da Mon Sep 17 00:00:00 2001
From: bohdan 
Date: Wed, 6 Sep 2017 17:12:16 +0300
Subject: [PATCH 195/226] Add method to AcknowledgementHandler interface to
 retry message only if it was not delivered before.

---
 .../acknowledge/AcknowledgementHandlerImpl.java  | 14 +++++++++++++-
 .../tcdl/msb/api/AcknowledgementHandler.java     | 10 ++++++++--
 .../AcknowledgementHandlerImplTest.java          | 16 +++++++++++++++-
 3 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java
index 1e23eea2..c507c151 100644
--- a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java
@@ -55,6 +55,15 @@ public void retryMessage() {
         });
     }
 
+    @Override
+    public void retryMessageIfNotRedelivered() {
+        if (!isMessageRedelivered) {
+            retryMessage();
+        } else {
+            rejectMessage();
+        }
+    }
+
     @Override
     public void rejectMessage() {
         executeAck("reject", () -> {
@@ -74,7 +83,8 @@ private void executeAck(String actionName, AckAction ackAction) {
             LOG.error(ACK_WAS_ALREADY_SENT, messageTextIdentifier);
         }
     }
-    
+
+    @Override
     public void autoConfirm() {
         executeAutoAck(() -> {
             confirmMessage();
@@ -82,6 +92,7 @@ public void autoConfirm() {
         });
     }
 
+    @Override
     public void autoReject() {
         executeAutoAck(() -> {
             rejectMessage();
@@ -89,6 +100,7 @@ public void autoReject() {
         });
     }
 
+    @Override
     public void autoRetry() {
         executeAutoAck(() -> {
             if (!isMessageRedelivered) {
diff --git a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java
index d6bbd7c4..31aa52a5 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java
@@ -10,7 +10,7 @@ public interface AcknowledgementHandler {
      * Set autoAcknowledgement value. 
      * @param autoAcknowledgement
      * If autoAcknowledgement is true:
-     * 1. A message can be confirmed/rejected by microservice developer in ResponderServer.process(() (see {@link ResponderServer})
+     * 1. A message can be confirmed/rejected by microservice developer in ResponderServer.process() (see {@link ResponderServer})
      * or Requester.onAcknowledge(), Requester.onResponse, Requester.onRawResponse() (see {@link Requester}) methods. 
      * 2. If a message is not confirmed/rejected during a message processing, 
      * acknowledgement will be automatically sent just after completion these methods by rules: 
@@ -21,7 +21,7 @@ public interface AcknowledgementHandler {
      * microservice developer MUST explicitly confirm/reject a message. 
      * autoAcknowledgement must be set to false if a message processing need to be continued in another thread. In this case
      * a message should be explicitly confirmed/rejected by microservice developer
-     * autoAcknowledgement is true b default.
+     * autoAcknowledgement is true by default.
      */
     void setAutoAcknowledgement(boolean autoAcknowledgement);
 
@@ -40,6 +40,12 @@ public interface AcknowledgementHandler {
      * Inform server that a message was rejected with requeue by consumer. 
      */
     void retryMessage();
+
+    /**
+     * Inform server that a message was rejected by customer. Message should be requeued only
+     * if it was not delivered before.
+     */
+    void retryMessageIfNotRedelivered();
     
     /**
      * Inform server that a message was rejected by consumer without requeue  
diff --git a/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java b/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java
index ed64971c..a72ddb18 100644
--- a/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java
@@ -77,7 +77,7 @@ public void testRedeliveredMessageRejected() throws Exception {
     }
 
     @Test
-    public void testRedeliveredMessageRejectedInsteadOfRetry() throws Exception {
+    public void testAutoRedeliveredMessageRejectedInsteadOfRetry() throws Exception {
         handler = getHandler(true);
         handler.autoRetry();
         verifySingleReject();
@@ -90,6 +90,20 @@ public void testManuallyRetriedMessageNotRejected() throws Exception {
         verifySingleRetry();
     }
 
+    @Test
+    public void testConditionallyRetriedMessageRetried() throws Exception {
+        handler = getHandler(false);
+        handler.retryMessageIfNotRedelivered();
+        verifySingleRetry();
+    }
+
+    @Test
+    public void testConditionallyRetriedRedeliveredMessageRejected() throws Exception {
+        handler = getHandler(true);
+        handler.retryMessageIfNotRedelivered();
+        verifySingleReject();
+    }
+
     @Test
     public void testRedeliveredMessageConfirmed() throws Exception {
         handler = getHandler(true);

From 790a433db29effc6c76cfbdc10e07c97ddc68822 Mon Sep 17 00:00:00 2001
From: bohdan 
Date: Fri, 8 Sep 2017 10:53:22 +0300
Subject: [PATCH 196/226] Change method name

---
 .../tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java      | 2 +-
 .../java/io/github/tcdl/msb/api/AcknowledgementHandler.java   | 2 +-
 .../tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java  | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java
index c507c151..b0f2b884 100644
--- a/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImpl.java
@@ -56,7 +56,7 @@ public void retryMessage() {
     }
 
     @Override
-    public void retryMessageIfNotRedelivered() {
+    public void retryMessageFirstTime() {
         if (!isMessageRedelivered) {
             retryMessage();
         } else {
diff --git a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java
index 31aa52a5..59c4b416 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/AcknowledgementHandler.java
@@ -45,7 +45,7 @@ public interface AcknowledgementHandler {
      * Inform server that a message was rejected by customer. Message should be requeued only
      * if it was not delivered before.
      */
-    void retryMessageIfNotRedelivered();
+    void retryMessageFirstTime();
     
     /**
      * Inform server that a message was rejected by consumer without requeue  
diff --git a/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java b/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java
index a72ddb18..1f28700f 100644
--- a/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/acknowledge/AcknowledgementHandlerImplTest.java
@@ -93,14 +93,14 @@ public void testManuallyRetriedMessageNotRejected() throws Exception {
     @Test
     public void testConditionallyRetriedMessageRetried() throws Exception {
         handler = getHandler(false);
-        handler.retryMessageIfNotRedelivered();
+        handler.retryMessageFirstTime();
         verifySingleRetry();
     }
 
     @Test
     public void testConditionallyRetriedRedeliveredMessageRejected() throws Exception {
         handler = getHandler(true);
-        handler.retryMessageIfNotRedelivered();
+        handler.retryMessageFirstTime();
         verifySingleReject();
     }
 

From 5ca0628f0f401c8ce10860b49c12b97e72aeec69 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 8 Sep 2017 16:38:21 +0300
Subject: [PATCH 197/226] 1.6.3 release preparation

---
 release-notes.html | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/release-notes.html b/release-notes.html
index 655a563e..2fb19ec0 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -4,16 +4,21 @@
 
 
 
-

Welcome to MSB-Java version 1.6.2

+

Welcome to MSB-Java version 1.6.3

-

July 3, 2017

+

September 8, 2017

+Changes in MSB-Java version 1.6.3:
+   - Added separate TRACE level log record when message body was put to error;
+   - Extended AcknowledgementHandler interface with retryMessageFirstTime() method. Message should be requeued only
+     if it was not delivered before;
+     
 Changes in MSB-Java version 1.6.2:
    - Manual message retry is not limited to a single attempt anymore. It is completely up to client code to decide 
      when message should not be retried anymore;
    - Restored CLI tool. Supported 'fanout' and 'topic' exchange types;
-   - Full message logging was replaced with separated message log with TRACE level;
+   - Full message logging was replaced with separated TRACE level message logging;
 
 Features of MSB-Java version 1.6.1:
    - Improved message processing during context shutdown;

From 20984c2a3ffe644a275222264562e7fc7cb16a47 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 8 Sep 2017 16:51:42 +0300
Subject: [PATCH 198/226] [maven-release-plugin] prepare release msb-java-1.6.3

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 cli/pom.xml                 | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index b13a6946..63d24dc2 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3-SNAPSHOT
+        1.6.3
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.3
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 1f008b0e..3b696ad2 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3-SNAPSHOT
+        1.6.3
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.3
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 5b9611ed..d4869db2 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3-SNAPSHOT
+        1.6.3
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.3
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 656bcffa..565b1541 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3-SNAPSHOT
+        1.6.3
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.3
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 45549303..356a3630 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3-SNAPSHOT
+        1.6.3
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.3
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 2b7cb860..bd44210c 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3-SNAPSHOT
+        1.6.3
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.3
     
 
     
diff --git a/pom.xml b/pom.xml
index b4748853..a7a7c431 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.3-SNAPSHOT
+    1.6.3
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.3
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index 73dd6daa..fd7b3fe3 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.3-SNAPSHOT
+		1.6.3
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		HEAD
+		msb-java-1.6.3
 	
 
 	

From 424ff63c931999049e52357e1959f3ef7ee636e2 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 8 Sep 2017 16:51:50 +0300
Subject: [PATCH 199/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 cli/pom.xml                 | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 63d24dc2..62604c09 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3
+        1.6.4-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.3
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 3b696ad2..e3466ff6 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3
+        1.6.4-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.3
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index d4869db2..a99e5d1d 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3
+        1.6.4-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.3
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 565b1541..4dacab54 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3
+        1.6.4-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.3
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 356a3630..bf8cba14 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3
+        1.6.4-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.3
+        HEAD
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index bd44210c..3dbf17e3 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.3
+        1.6.4-SNAPSHOT
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.3
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index a7a7c431..ffdfca3f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.3
+    1.6.4-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.3
+        HEAD
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index fd7b3fe3..e584192f 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.3
+		1.6.4-SNAPSHOT
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		msb-java-1.6.3
+		HEAD
 	
 
 	

From d3a63e491cbbea887c91c288f6d7f65e685e1427 Mon Sep 17 00:00:00 2001
From: Kim Eeckhout 
Date: Tue, 24 Oct 2017 18:12:12 +0200
Subject: [PATCH 200/226] IN-3160: exposing the message count in the Consumer

---
 .../adapters/amqp/AmqpConsumerAdapter.java    | 22 ++++++-
 .../amqp/AmqpConsumerAdapterTest.java         | 58 +++++++++++++++++++
 .../io/github/tcdl/msb/ChannelManager.java    |  5 ++
 .../java/io/github/tcdl/msb/Consumer.java     |  9 +++
 .../tcdl/msb/adapters/ConsumerAdapter.java    |  9 +++
 .../github/tcdl/msb/api/ResponderServer.java  |  9 +++
 .../tcdl/msb/impl/ResponderServerImpl.java    |  5 ++
 .../github/tcdl/msb/ChannelManagerTest.java   | 42 ++++++++++++++
 .../java/io/github/tcdl/msb/ConsumerTest.java | 10 ++++
 .../msb/impl/ResponderServerImplTest.java     | 22 +++++++
 .../TestMsbConsumerAdapter.java               |  6 ++
 11 files changed, 195 insertions(+), 2 deletions(-)

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
index 4c44ecd9..5aa663fa 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
@@ -9,6 +9,7 @@
 import org.apache.commons.lang3.Validate;
 
 import java.io.IOException;
+import java.util.Optional;
 import java.util.Set;
 
 
@@ -20,10 +21,10 @@ public class AmqpConsumerAdapter implements ConsumerAdapter {
     private String consumerTag;
     private AmqpBrokerConfig adapterConfig;
     private boolean isResponseTopic = false;
+    private Optional currentQueueName = Optional.empty();
 
     public AmqpConsumerAdapter(String exchangeName, ExchangeType exchangeType, Set bindingKeys, AmqpBrokerConfig amqpBrokerConfig,
                                AmqpConnectionManager connectionManager, boolean isResponseTopic) {
-
         Validate.notNull(exchangeName, "Exchange name is required");
         Validate.notNull(exchangeType, "Exchange type is required");
         Validate.notEmpty(bindingKeys, "At least one routing key is required");
@@ -52,13 +53,15 @@ public void subscribe(RawMessageHandler msgHandler) {
 
         String queueName = generateQueueName(exchangeName, groupId, durable);
 
+
         try {
             channel.queueDeclare(queueName, durable /* durable */, false /* exclusive */, !durable /*auto-delete */, null);
             channel.basicQos(prefetchCount); // Don't accept more messages if we have any unacknowledged
-            for(String bindingKey : bindingKeys) {
+            for (String bindingKey : bindingKeys) {
                 channel.queueBind(queueName, exchangeName, bindingKey);
             }
             consumerTag = channel.basicConsume(queueName, false /* autoAck */, new AmqpMessageConsumer(channel, msgHandler, adapterConfig));
+            currentQueueName = Optional.of(queueName);
         } catch (IOException e) {
             throw new ChannelException(String.format("Failed to subscribe to topic %s with routing keys %s", exchangeName, bindingKeys), e);
         }
@@ -79,11 +82,26 @@ protected boolean isDurable() {
     public void unsubscribe() {
         try {
             channel.basicCancel(consumerTag);
+            currentQueueName = Optional.empty();
         } catch (IOException e) {
             throw new ChannelException(String.format("Failed to unsubscribe from topic %s", exchangeName), e);
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Optional messageCount() {
+        return currentQueueName.map(queueName -> {
+            try {
+                return channel.messageCount(queueName);
+            } catch (IOException e) {
+                throw new ChannelException(String.format("Failed to fetch ready messages for topic %s and queue %s", exchangeName, queueName), e);
+            }
+        });
+    }
+
     /**
      * Generate topic name to get unique topics for different microservices
      *
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
index 58e17e01..5225993a 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
@@ -227,6 +227,64 @@ public void testIsDurableTrueIfNotResponseTopicAndDurableConfig() throws IOExcep
         assertTrue(adapter.isDurable());
     }
 
+    @Test
+    public void testMessageCount() throws Exception {
+        String topicName = "myTopic";
+        String groupId = "groupId";
+        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf(topicName, groupId, false);
+
+        when(mockChannel.messageCount(anyString())).thenReturn(42L);
+
+        adapter.subscribe((jsonMessage, ackHandler) -> {
+        });
+
+        verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null);
+
+        Optional answer = Optional.of(42L);
+        Optional result = adapter.messageCount();
+        verify(mockChannel, times(1)).messageCount(anyString());
+        assertEquals(answer, result);
+    }
+
+    @Test
+    public void testMessageCountNeverSubscribed() throws Exception {
+        String topicName = "myTopic";
+        String groupId = "groupId";
+        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf(topicName, groupId, false);
+
+
+        Optional result = adapter.messageCount();
+        verify(mockChannel, never()).messageCount(anyString());
+        assertEquals(result, Optional.empty());
+    }
+
+    @Test
+    public void testMessageCountAfterUnsubscribe() throws Exception {
+        String topicName = "myTopic";
+        String groupId = "groupId";
+        AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf(topicName, groupId, false);
+
+        when(mockChannel.messageCount(anyString())).thenReturn(42L);
+
+        adapter.subscribe((jsonMessage, ackHandler) -> {
+        });
+
+        verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null);
+
+        Optional expectedAnswerWhileSubscribed = Optional.of(42L);
+        Optional expectedAnswerWhileUnsubscribed = Optional.empty();
+
+        Optional resultWhileSubscribed = adapter.messageCount();
+
+        adapter.unsubscribe();
+
+        Optional resultWhileUnsubscribed = adapter.messageCount();
+
+        verify(mockChannel, times(1)).messageCount(anyString());
+        assertEquals(resultWhileSubscribed, expectedAnswerWhileSubscribed);
+        assertEquals(resultWhileUnsubscribed, expectedAnswerWhileUnsubscribed);
+    }
+
     private AmqpConsumerAdapter createAdapterWithNonDurableConf(String topic, String groupId, boolean isResponseTopic) {
         boolean isDurableConf = false;
         AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(),
diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
index a228454b..f679ef2a 100644
--- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
@@ -19,6 +19,7 @@
 
 import java.time.Clock;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -63,6 +64,10 @@ public Producer findOrCreateProducer(String topic, RequestOptions requestOptions
         return producer;
     }
 
+    public Optional getAvailableMessageCount(String topic) {
+        return Optional.ofNullable(consumersByTopic.get(topic)).flatMap(Consumer::messageCount);
+    }
+
     /**
      * Start consuming messages on specified topic with handler.
      * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages.
diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java
index 3184e5bd..804c8b8c 100644
--- a/core/src/main/java/io/github/tcdl/msb/Consumer.java
+++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java
@@ -92,6 +92,15 @@ public void end() {
         rawAdapter.unsubscribe();
     }
 
+    /**
+     * Returns the number of messages in the queue, ready to be delivered to consumers.
+     * If the queue has not been subscribed to yet, this will return {@link Optional#empty()}.
+     * @return the number of messages in ready state
+     */
+    public Optional messageCount() {
+        return rawAdapter.messageCount();
+    }
+
     /**
      * Process raw incoming message JSON. If Message JSON is invalid or the message has been expired, the message
      * will be rejected by means of {@link AcknowledgementHandlerInternal}.
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java
index 11d57460..43ae4f9a 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java
@@ -3,6 +3,8 @@
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
 import io.github.tcdl.msb.api.exception.ChannelException;
 
+import java.util.Optional;
+
 /**
  * {@link ConsumerAdapter} allows to receive messages from message bus. One adapter instance is associated with specific topic.
  *
@@ -23,6 +25,13 @@ public interface ConsumerAdapter {
      */
     void unsubscribe();
 
+    /**
+     * Returns the number of messages in the queue, ready to be delivered to consumers.
+     * If the queue has not been subscribed to yet, this will return {@link Optional#empty()}.
+     * @return the number of messages in ready state
+     */
+    Optional messageCount();
+
     /**
      * Callback interface for incoming message handler
      */
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java b/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
index e783ea7e..6250c7cc 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
@@ -2,6 +2,8 @@
 
 import io.github.tcdl.msb.api.message.Message;
 
+import java.util.Optional;
+
 /**
  * {@link ResponderServer} enable user to listen on messages from the bus and executing microservice business logic.
  * Call to {@link #listen()} method will start listening on incoming messages from the bus.
@@ -24,6 +26,13 @@ public interface ResponderServer {
      */
     ResponderServer stop();
 
+    /**
+     * When listening on the specified topic, returns the available messages on that topic.
+     * When not listening, returns {@link Optional#empty()}
+     * @return
+     */
+    Optional availableMessageCount();
+
     /**
      * Implementation of this interface contains business logic processed by microservice.
      */
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
index c6627a38..e0b4b6a1 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
@@ -80,6 +80,11 @@ public ResponderServer stop(){
         return this;
     }
 
+    @Override
+    public Optional availableMessageCount() {
+        return msbContext.getChannelManager().getAvailableMessageCount(namespace);
+    }
+
     Responder createResponder(Message incomingMessage) {
         if (isResponseNeeded(incomingMessage)) {
             return new ResponderImpl(responderOptions.getMessageTemplate(), incomingMessage, msbContext);
diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java
index 7b1f8cb6..946289bb 100644
--- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java
@@ -21,6 +21,7 @@
 import javax.xml.ws.Holder;
 import java.time.Clock;
 import java.util.Collections;
+import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -103,4 +104,45 @@ public void testReceiveMessageInvokesHandler() throws InterruptedException {
         assertNotNull(messageEvent.value);
     }
 
+    @Test
+    public void testAvailableMessageCountInitialized() {
+        String topic = "some:topic";
+
+        Optional result = channelManager.getAvailableMessageCount(topic);
+
+        assertEquals(Optional.empty(), result);
+    }
+
+    @Test
+    public void testAvailableMessageCountSubscribed() {
+        String topic = "some:topic";
+
+        ResponderOptions responderOptions = new ResponderOptions.Builder().build();
+
+        channelManager.subscribe(topic, responderOptions, (message, acknowledgeHandler) -> {});
+
+        expectedException.expect(UnsupportedOperationException.class);
+        channelManager.getAvailableMessageCount(topic);
+    }
+
+    @Test
+    public void testAvailableMessageCountUnsubscribed() {
+        String topic = "some:topic";
+
+        ResponderOptions responderOptions = new ResponderOptions.Builder().build();
+
+        channelManager.subscribe(topic, responderOptions, (message, acknowledgeHandler) -> {});
+
+        expectedException.expect(UnsupportedOperationException.class);
+        channelManager.getAvailableMessageCount(topic);
+
+        channelManager.unsubscribe(topic);
+
+        Optional result = channelManager.getAvailableMessageCount(topic);
+
+        assertEquals(Optional.empty(), result);
+
+
+    }
+
 }
\ No newline at end of file
diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
index 3c0ed2d4..2fe8bc0a 100644
--- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java
@@ -195,6 +195,16 @@ public void testExceptionWhileMessageConvertingProcessedBySubscriber() throws Js
         verifyMessageNotHandled();
     }
 
+    @Test
+    public void testMessageCount() {
+        Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper);
+        Optional answer = Optional.of(42L);
+        when(adapterMock.messageCount()).thenReturn(answer);
+        Optional result = consumer.messageCount();
+        verify(adapterMock, times(1)).messageCount();
+        assertEquals(answer, result);
+    }
+
     @Test
     public void testHandleRawMessageConsumeFromTopicSkipValidation() {
         MsbConfig msbConf = spy(TestUtils.createMsbConfigurations());
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
index ffbf4494..b48634b7 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
@@ -15,6 +15,7 @@
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
@@ -78,6 +79,27 @@ public void testResponderServerProcessPayloadSuccess() throws Exception {
         verify(spyResponderServer).onResponder(anyObject());
     }
 
+    @Test
+    public void testResponderServerAvailableMessageCount() {
+        ResponderServer.RequestHandler, Object, Map>> handler =
+                (request, responderContext) -> {};
+        ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.emptySet()).withMessageTemplate(messageTemplate).build();
+        MsbContextImpl spyMsbContext = spy(msbContext);
+
+        ChannelManager spyChannelManager = spy(msbContext.getChannelManager());
+        when(spyMsbContext.getChannelManager()).thenReturn(spyChannelManager);
+        when(spyChannelManager.getAvailableMessageCount(anyString())).thenReturn(Optional.of(666L));
+
+        ResponderServerImpl, Object, Map>> responderServer =
+                ResponderServerImpl.create(TOPIC,responderOptions, spyMsbContext, handler, null,
+                        new TypeReference, Object, Map>>() {});
+
+        Optional result = responderServer.availableMessageCount();
+
+        verify(spyChannelManager, times(1)).getAvailableMessageCount(TOPIC);
+        assertEquals(Optional.of(666L), result);
+    }
+
     @Test(expected = NullPointerException.class)
     public void testResponderServerProcessErrorNoHandler() throws Exception {
         msbContext.getObjectFactory().createResponderServer(TOPIC, messageTemplate, null);
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java
index 439deb28..09a978d3 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java
@@ -4,6 +4,7 @@
 import io.github.tcdl.msb.adapters.ConsumerAdapter;
 
 import java.util.HashSet;
+import java.util.Optional;
 import java.util.Set;
 
 import static org.mockito.Mockito.mock;
@@ -30,6 +31,11 @@ public void unsubscribe() {
 
     }
 
+    @Override
+    public Optional messageCount() {
+        throw new UnsupportedOperationException("This method is not implemented in this test class.");
+    }
+
     public void pushTestMessage(String jsonMessage) {
         AcknowledgementHandlerInternal ackHandler = mock(AcknowledgementHandlerInternal.class);
         rawMessageHandlers.forEach((handler)-> handler.onMessage(jsonMessage, ackHandler));

From 82faf17112eaf25d10c8b057b68da5e32c48890d Mon Sep 17 00:00:00 2001
From: rdro-tc 
Date: Tue, 31 Oct 2017 11:18:27 +0200
Subject: [PATCH 201/226] IN-3160: added message count metric to responder
 server

---
 .../adapters/amqp/AmqpConsumerAdapter.java    |  1 -
 .../amqp/AmqpConsumerAdapterTest.java         |  6 ++--
 .../github/tcdl/msb/api/ResponderServer.java  |  9 ++----
 .../io/github/tcdl/msb/api/metrics/Gauge.java | 15 ++++++++++
 .../github/tcdl/msb/api/metrics/Metric.java   |  7 +++++
 .../tcdl/msb/api/metrics/MetricSet.java       | 29 +++++++++++++++++++
 .../tcdl/msb/impl/ResponderServerImpl.java    | 10 +++++--
 .../msb/impl/ResponderServerImplTest.java     | 12 +++++---
 8 files changed, 72 insertions(+), 17 deletions(-)
 create mode 100644 core/src/main/java/io/github/tcdl/msb/api/metrics/Gauge.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/api/metrics/Metric.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java

diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
index 5aa663fa..f9ac8dc7 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java
@@ -53,7 +53,6 @@ public void subscribe(RawMessageHandler msgHandler) {
 
         String queueName = generateQueueName(exchangeName, groupId, durable);
 
-
         try {
             channel.queueDeclare(queueName, durable /* durable */, false /* exclusive */, !durable /*auto-delete */, null);
             channel.basicQos(prefetchCount); // Don't accept more messages if we have any unacknowledged
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
index 5225993a..8b6fde01 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
+++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java
@@ -255,7 +255,7 @@ public void testMessageCountNeverSubscribed() throws Exception {
 
         Optional result = adapter.messageCount();
         verify(mockChannel, never()).messageCount(anyString());
-        assertEquals(result, Optional.empty());
+        assertEquals(Optional.empty(), result);
     }
 
     @Test
@@ -281,8 +281,8 @@ public void testMessageCountAfterUnsubscribe() throws Exception {
         Optional resultWhileUnsubscribed = adapter.messageCount();
 
         verify(mockChannel, times(1)).messageCount(anyString());
-        assertEquals(resultWhileSubscribed, expectedAnswerWhileSubscribed);
-        assertEquals(resultWhileUnsubscribed, expectedAnswerWhileUnsubscribed);
+        assertEquals(expectedAnswerWhileSubscribed, resultWhileSubscribed);
+        assertEquals(expectedAnswerWhileUnsubscribed, resultWhileUnsubscribed);
     }
 
     private AmqpConsumerAdapter createAdapterWithNonDurableConf(String topic, String groupId, boolean isResponseTopic) {
diff --git a/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java b/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
index 6250c7cc..f594798d 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/ResponderServer.java
@@ -1,8 +1,7 @@
 package io.github.tcdl.msb.api;
 
 import io.github.tcdl.msb.api.message.Message;
-
-import java.util.Optional;
+import io.github.tcdl.msb.api.metrics.MetricSet;
 
 /**
  * {@link ResponderServer} enable user to listen on messages from the bus and executing microservice business logic.
@@ -27,11 +26,9 @@ public interface ResponderServer {
     ResponderServer stop();
 
     /**
-     * When listening on the specified topic, returns the available messages on that topic.
-     * When not listening, returns {@link Optional#empty()}
-     * @return
+        Returns set of metrics related to the current responder server
      */
-    Optional availableMessageCount();
+    MetricSet getMetrics();
 
     /**
      * Implementation of this interface contains business logic processed by microservice.
diff --git a/core/src/main/java/io/github/tcdl/msb/api/metrics/Gauge.java b/core/src/main/java/io/github/tcdl/msb/api/metrics/Gauge.java
new file mode 100644
index 00000000..0d03639f
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/api/metrics/Gauge.java
@@ -0,0 +1,15 @@
+package io.github.tcdl.msb.api.metrics;
+
+/**
+ * A gauge metric is an instantaneous reading of a particular value
+ * @param  the type of the metric's value
+ */
+
+@FunctionalInterface
+public interface Gauge extends Metric {
+
+    /**
+     * @return the metric's current value
+     */
+    T getValue();
+}
\ No newline at end of file
diff --git a/core/src/main/java/io/github/tcdl/msb/api/metrics/Metric.java b/core/src/main/java/io/github/tcdl/msb/api/metrics/Metric.java
new file mode 100644
index 00000000..331350d2
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/api/metrics/Metric.java
@@ -0,0 +1,7 @@
+package io.github.tcdl.msb.api.metrics;
+
+/**
+ * A marker interface to indicate that a class is a metric.
+ */
+public interface Metric {
+}
\ No newline at end of file
diff --git a/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java b/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java
new file mode 100644
index 00000000..fcd15910
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java
@@ -0,0 +1,29 @@
+package io.github.tcdl.msb.api.metrics;
+
+import java.util.Map;
+
+/**
+ * A set of named metrics.
+ */
+public interface MetricSet extends Metric {
+
+    /**
+     * {@value #MESSAGE_COUNT_METRIC} metric key for the number available messages as {@link Gauge} of {@link Long} type
+     */
+    String MESSAGE_COUNT_METRIC = "availableMessageCount";
+
+    /**
+     * @return supported metric by name
+     */
+    default Metric getMetric(String metricName) {
+        return getMetrics().get(metricName);
+    }
+
+    /**
+     * A map of metric names to metrics.
+     *
+     * @return the metrics
+     */
+    Map getMetrics();
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
index e0b4b6a1..5de9a1c4 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java
@@ -2,19 +2,22 @@
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableMap;
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.MessageHandler;
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Message;
+import io.github.tcdl.msb.api.metrics.Gauge;
+import io.github.tcdl.msb.api.metrics.MetricSet;
 import io.github.tcdl.msb.support.Utils;
 import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Optional;
-import java.util.Set;
 
 public class ResponderServerImpl implements ResponderServer {
+
     private static final Logger LOG = LoggerFactory.getLogger(ResponderServerImpl.class);
 
     private String namespace;
@@ -81,8 +84,9 @@ public ResponderServer stop(){
     }
 
     @Override
-    public Optional availableMessageCount() {
-        return msbContext.getChannelManager().getAvailableMessageCount(namespace);
+    public MetricSet getMetrics() {
+        Gauge messageCountMetric = () -> msbContext.getChannelManager().getAvailableMessageCount(namespace).orElse(null);
+        return () -> ImmutableMap.of(MetricSet.MESSAGE_COUNT_METRIC, messageCountMetric);
     }
 
     Responder createResponder(Message incomingMessage) {
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
index b48634b7..c0d6544e 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
@@ -8,6 +8,7 @@
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.api.message.payload.RestPayload;
+import io.github.tcdl.msb.api.metrics.Gauge;
 import io.github.tcdl.msb.support.TestUtils;
 import org.junit.Before;
 import org.junit.Test;
@@ -25,6 +26,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.same;
 
 public class ResponderServerImplTest {
 
@@ -90,14 +92,16 @@ public void testResponderServerAvailableMessageCount() {
         when(spyMsbContext.getChannelManager()).thenReturn(spyChannelManager);
         when(spyChannelManager.getAvailableMessageCount(anyString())).thenReturn(Optional.of(666L));
 
-        ResponderServerImpl, Object, Map>> responderServer =
+        ResponderServer responderServer =
                 ResponderServerImpl.create(TOPIC,responderOptions, spyMsbContext, handler, null,
-                        new TypeReference, Object, Map>>() {});
+                        new TypeReference, Object, Map>>() {})
+                .listen();
 
-        Optional result = responderServer.availableMessageCount();
+        Gauge availableMessageCount = (Gauge) responderServer.getMetrics().getMetric("availableMessageCount");
 
+        assertEquals(666L, availableMessageCount.getValue());
+        verify(spyChannelManager, times(1)).subscribe(eq(TOPIC), any(ResponderOptions.class), any(MessageHandler.class));
         verify(spyChannelManager, times(1)).getAvailableMessageCount(TOPIC);
-        assertEquals(Optional.of(666L), result);
     }
 
     @Test(expected = NullPointerException.class)

From c3288681659a02effd30952fa135bb558ea74964 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 3 Nov 2017 14:06:46 +0200
Subject: [PATCH 202/226] 1.6.4 release preparation

---
 release-notes.html | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/release-notes.html b/release-notes.html
index 2fb19ec0..352c5ca7 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -4,11 +4,14 @@
 
 
 
-

Welcome to MSB-Java version 1.6.3

+

Welcome to MSB-Java version 1.6.4

-

September 8, 2017

+

November 3, 2017

+Changes in MSB-Java version 1.6.4:
+   - Exposed Message Count Metric for ResponderServer
+
 Changes in MSB-Java version 1.6.3:
    - Added separate TRACE level log record when message body was put to error;
    - Extended AcknowledgementHandler interface with retryMessageFirstTime() method. Message should be requeued only

From 97d4d9f36bf64a49f838a62366cfab2b253710ca Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 3 Nov 2017 14:11:48 +0200
Subject: [PATCH 203/226] [maven-release-plugin] prepare release msb-java-1.6.4

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 cli/pom.xml                 | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 62604c09..1ce7110c 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4-SNAPSHOT
+        1.6.4
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.4
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index e3466ff6..72bbda65 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4-SNAPSHOT
+        1.6.4
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.4
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index a99e5d1d..c0abcbbc 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4-SNAPSHOT
+        1.6.4
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.4
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 4dacab54..bbf71274 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4-SNAPSHOT
+        1.6.4
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.4
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index bf8cba14..581cba67 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4-SNAPSHOT
+        1.6.4
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.4
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 3dbf17e3..6246d971 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4-SNAPSHOT
+        1.6.4
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.4
     
 
     
diff --git a/pom.xml b/pom.xml
index ffdfca3f..aa65634e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.4-SNAPSHOT
+    1.6.4
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.4
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index e584192f..97685d30 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.4-SNAPSHOT
+		1.6.4
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		HEAD
+		msb-java-1.6.4
 	
 
 	

From 9d3b5385dd325395aa6f7c446b9580c23bd4bb3d Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Fri, 3 Nov 2017 14:11:57 +0200
Subject: [PATCH 204/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 cli/pom.xml                 | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 1ce7110c..1ffc22e7 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4
+        1.6.5-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.4
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 72bbda65..7f9ee86d 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4
+        1.6.5-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.4
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index c0abcbbc..5bd84262 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4
+        1.6.5-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.4
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index bbf71274..d1495bec 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4
+        1.6.5-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.4
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 581cba67..fd8536cb 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4
+        1.6.5-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.4
+        HEAD
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 6246d971..493b6331 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.4
+        1.6.5-SNAPSHOT
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.4
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index aa65634e..1143e0c0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.4
+    1.6.5-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.4
+        HEAD
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index 97685d30..ad1db3e6 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.4
+		1.6.5-SNAPSHOT
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		msb-java-1.6.4
+		HEAD
 	
 
 	

From e40e9f6b467f951867f273fa8b1a41b52d44212f Mon Sep 17 00:00:00 2001
From: Sergey Ivanov 
Date: Fri, 30 Mar 2018 10:47:59 +0300
Subject: [PATCH 205/226] make msb-spring-boot-starter compatible with
 SpringBoot 2 release

---
 release-notes.html                                           | 5 ++++-
 spring-boot-starter/pom.xml                                  | 2 +-
 .../java/io/github/tcdl/msb/autoconfigure/MsbProperties.java | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/release-notes.html b/release-notes.html
index 352c5ca7..01ec435e 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -6,9 +6,12 @@
 
 

Welcome to MSB-Java version 1.6.4

-

November 3, 2017

+

March 30, 2018

+Changes in MSB-Java version 1.6.5:
+   - Upgrade msb-spring-boot-starter to Spring Boot 2 release
+
 Changes in MSB-Java version 1.6.4:
    - Exposed Message Count Metric for ResponderServer
 
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index ad1db3e6..ce00726e 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -83,7 +83,7 @@
 			
 				org.springframework.boot
 				spring-boot-dependencies
-				1.4.0.RELEASE
+				2.0.0.RELEASE
 				pom
 				import
 			
diff --git a/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbProperties.java b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbProperties.java
index f39d3500..bb5d739e 100644
--- a/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbProperties.java
+++ b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbProperties.java
@@ -4,7 +4,7 @@
 
 import java.nio.charset.Charset;
 
-@ConfigurationProperties("msbConfig")
+@ConfigurationProperties("msb-config")
 public class MsbProperties {
 
     ServiceDetails serviceDetails = new ServiceDetails();

From 503373296f41f427c4cc47ef0d38346ea32e7299 Mon Sep 17 00:00:00 2001
From: Sergey Ivanov 
Date: Fri, 30 Mar 2018 15:16:06 +0300
Subject: [PATCH 206/226] missed version pointer

---
 release-notes.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/release-notes.html b/release-notes.html
index 01ec435e..f0bf499f 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -4,7 +4,7 @@
 
 
 
-

Welcome to MSB-Java version 1.6.4

+

Welcome to MSB-Java version 1.6.5

March 30, 2018

From 23e8272159b9586ef9d509234beca62befa9d61d Mon Sep 17 00:00:00 2001 From: rdro-tc Date: Tue, 27 Mar 2018 18:21:25 +0300 Subject: [PATCH 207/226] POC: consumer count --- .../msb/adapters/amqp/AmqpAdapterFactory.java | 1 + .../adapters/amqp/AmqpConsumerAdapter.java | 14 ++++ .../amqp/AmqpConsumerAdapterTest.java | 70 +++++++++++++++++++ .../io/github/tcdl/msb/ChannelManager.java | 4 ++ .../java/io/github/tcdl/msb/Consumer.java | 26 ++++--- .../tcdl/msb/adapters/ConsumerAdapter.java | 6 ++ .../tcdl/msb/api/metrics/MetricSet.java | 5 ++ .../tcdl/msb/impl/ResponderServerImpl.java | 5 +- .../github/tcdl/msb/ChannelManagerTest.java | 36 ++++++++++ .../java/io/github/tcdl/msb/ConsumerTest.java | 10 +++ .../msb/impl/ResponderServerImplTest.java | 17 +++-- .../TestMsbConsumerAdapter.java | 5 ++ 12 files changed, 183 insertions(+), 16 deletions(-) diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java index 681d6e57..50766041 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java @@ -115,6 +115,7 @@ protected ConnectionFactory createConnectionFactory(AmqpBrokerConfig adapterConf connectionFactory.setHost(host); connectionFactory.setPort(port); connectionFactory.setAutomaticRecoveryEnabled(true); + connectionFactory.setTopologyRecoveryEnabled(true); connectionFactory.setNetworkRecoveryInterval(adapterConfig.getNetworkRecoveryIntervalMs()); connectionFactory.setRequestedHeartbeat(adapterConfig.getHeartbeatIntervalSec()); connectionFactory.setExceptionHandler(new AmqpExceptionHandler()); diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java index f9ac8dc7..e1b1190d 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapter.java @@ -101,6 +101,20 @@ public Optional messageCount() { }); } + /** + * {@inheritDoc} + */ + @Override + public Optional isConnected() { + return currentQueueName.map(queueName -> { + try { + return channel.isOpen() && channel.getConnection().isOpen() && channel.consumerCount(queueName) > 0; + } catch (IOException e) { + throw new ChannelException(String.format("Failed to get consumer status for topic %s and queue %s", exchangeName, queueName), e); + } + }); + } + /** * Generate topic name to get unique topics for different microservices * diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java index 8b6fde01..1cedd268 100644 --- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java +++ b/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/AmqpConsumerAdapterTest.java @@ -285,6 +285,76 @@ public void testMessageCountAfterUnsubscribe() throws Exception { assertEquals(expectedAnswerWhileUnsubscribed, resultWhileUnsubscribed); } + @Test + public void testIsConnected() throws Exception { + String topicName = "myTopic"; + String groupId = "groupId"; + AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf(topicName, groupId, false); + + Connection mockConnection = mock(Connection.class); + when(mockConnection.isOpen()).thenReturn(true); + when(mockChannel.isOpen()).thenReturn(true); + when(mockChannel.getConnection()).thenReturn(mockConnection); + when(mockChannel.consumerCount(anyString())).thenReturn(1L); + + adapter.subscribe((jsonMessage, ackHandler) -> { + }); + + verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null); + + Optional answer = Optional.of(true); + Optional result = adapter.isConnected(); + verify(mockChannel, times(1)).isOpen(); + verify(mockConnection, times(1)).isOpen(); + verify(mockChannel, times(1)).consumerCount(anyString()); + assertEquals(answer, result); + } + + @Test + public void testIsConnectedNeverSubscribed() throws Exception { + String topicName = "myTopic"; + String groupId = "groupId"; + AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf(topicName, groupId, false); + + Optional result = adapter.isConnected(); + verify(mockChannel, never()).isOpen(); + assertEquals(Optional.empty(), result); + } + + @Test + public void testIsConnectedAfterUnsubscribe() throws Exception { + String topicName = "myTopic"; + String groupId = "groupId"; + AmqpConsumerAdapter adapter = createAdapterWithNonDurableConf(topicName, groupId, false); + + Connection mockConnection = mock(Connection.class); + when(mockConnection.isOpen()).thenReturn(true); + when(mockChannel.isOpen()).thenReturn(true); + when(mockChannel.getConnection()).thenReturn(mockConnection); + when(mockChannel.consumerCount(anyString())).thenReturn(1L); + + adapter.subscribe((jsonMessage, ackHandler) -> { + }); + + verify(mockChannel).exchangeDeclare(topicName, "fanout", false, true, null); + + Optional expectedAnswerWhileSubscribed = Optional.of(true); + Optional expectedAnswerWhileUnsubscribed = Optional.empty(); + + Optional resultWhileSubscribed = adapter.isConnected(); + + adapter.unsubscribe(); + + Optional resultWhileUnsubscribed = adapter.isConnected(); + + verify(mockChannel, times(1)).isOpen(); + verify(mockConnection, times(1)).isOpen(); + verify(mockChannel, times(1)).consumerCount(anyString()); + + assertEquals(expectedAnswerWhileSubscribed, resultWhileSubscribed); + assertEquals(expectedAnswerWhileUnsubscribed, resultWhileUnsubscribed); + } + private AmqpConsumerAdapter createAdapterWithNonDurableConf(String topic, String groupId, boolean isResponseTopic) { boolean isDurableConf = false; AmqpBrokerConfig nondurableAmqpConfig = new AmqpBrokerConfig(Charset.forName("UTF-8"), "127.0.0.1", 10, Optional.empty(), Optional.empty(), Optional.empty(), diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java index f679ef2a..3940755e 100644 --- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java +++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java @@ -68,6 +68,10 @@ public Optional getAvailableMessageCount(String topic) { return Optional.ofNullable(consumersByTopic.get(topic)).flatMap(Consumer::messageCount); } + public Optional isConnected(String topic) { + return Optional.ofNullable(consumersByTopic.get(topic)).flatMap(Consumer::isConnected); + } + /** * Start consuming messages on specified topic with handler. * Calls to subscribe() and unsubscribe() have to be properly synchronized by client code not to lose messages. diff --git a/core/src/main/java/io/github/tcdl/msb/Consumer.java b/core/src/main/java/io/github/tcdl/msb/Consumer.java index 804c8b8c..d3fa9d09 100644 --- a/core/src/main/java/io/github/tcdl/msb/Consumer.java +++ b/core/src/main/java/io/github/tcdl/msb/Consumer.java @@ -1,28 +1,26 @@ package io.github.tcdl.msb; -import io.github.tcdl.msb.adapters.ConsumerAdapter; +import com.fasterxml.jackson.databind.ObjectMapper; import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal; -import io.github.tcdl.msb.threading.MessageHandlerInvoker; +import io.github.tcdl.msb.adapters.ConsumerAdapter; import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.MetaMessage; import io.github.tcdl.msb.collector.ConsumedMessagesAwareMessageHandler; import io.github.tcdl.msb.config.MsbConfig; import io.github.tcdl.msb.support.JsonValidator; import io.github.tcdl.msb.support.Utils; - -import java.time.Clock; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Optional; - +import io.github.tcdl.msb.threading.MessageHandlerInvoker; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.MDC; +import java.time.Clock; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Optional; + /** * {@link Consumer} is a component responsible for consuming messages from the bus. */ @@ -101,6 +99,14 @@ public Optional messageCount() { return rawAdapter.messageCount(); } + /** + * Returns a connection status of the consumer + * @return if a consumer connected to the broker + */ + public Optional isConnected() { + return rawAdapter.isConnected(); + } + /** * Process raw incoming message JSON. If Message JSON is invalid or the message has been expired, the message * will be rejected by means of {@link AcknowledgementHandlerInternal}. diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java b/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java index 43ae4f9a..ba63d8b2 100644 --- a/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java +++ b/core/src/main/java/io/github/tcdl/msb/adapters/ConsumerAdapter.java @@ -32,6 +32,12 @@ public interface ConsumerAdapter { */ Optional messageCount(); + /** + * Returns a connection status of the consumer + * @return if a consumer connected to the broker + */ + Optional isConnected(); + /** * Callback interface for incoming message handler */ diff --git a/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java b/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java index fcd15910..5ee147cf 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java +++ b/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java @@ -12,6 +12,11 @@ public interface MetricSet extends Metric { */ String MESSAGE_COUNT_METRIC = "availableMessageCount"; + /** + * {@value #CONSUMER_CONNECTED_METRIC} metric key for the consumer status {@link Gauge} of {@link Long} type + */ + String CONSUMER_CONNECTED_METRIC = "consumerConnected"; + /** * @return supported metric by name */ diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java index 5de9a1c4..25f51ea4 100644 --- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java +++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderServerImpl.java @@ -86,7 +86,10 @@ public ResponderServer stop(){ @Override public MetricSet getMetrics() { Gauge messageCountMetric = () -> msbContext.getChannelManager().getAvailableMessageCount(namespace).orElse(null); - return () -> ImmutableMap.of(MetricSet.MESSAGE_COUNT_METRIC, messageCountMetric); + Gauge consumerConnectedMetric = () -> msbContext.getChannelManager().isConnected(namespace).orElse(null); + return () -> ImmutableMap.of( + MetricSet.MESSAGE_COUNT_METRIC, messageCountMetric, + MetricSet.CONSUMER_CONNECTED_METRIC, consumerConnectedMetric); } Responder createResponder(Message incomingMessage) { diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java index 946289bb..6386abb5 100644 --- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java @@ -141,8 +141,44 @@ public void testAvailableMessageCountUnsubscribed() { Optional result = channelManager.getAvailableMessageCount(topic); assertEquals(Optional.empty(), result); + } + + @Test + public void testIsConsumerConnectedInitialized() { + String topic = "some:topic"; + Optional result = channelManager.isConnected(topic); + assertEquals(Optional.empty(), result); + } + + @Test + public void testConsumerCountSubscribed() { + String topic = "some:topic"; + + ResponderOptions responderOptions = new ResponderOptions.Builder().build(); + + channelManager.subscribe(topic, responderOptions, (message, acknowledgeHandler) -> {}); + + expectedException.expect(UnsupportedOperationException.class); + channelManager.isConnected(topic); } + @Test + public void testConsumerCountUnsubscribed() { + String topic = "some:topic"; + + ResponderOptions responderOptions = new ResponderOptions.Builder().build(); + + channelManager.subscribe(topic, responderOptions, (message, acknowledgeHandler) -> {}); + + expectedException.expect(UnsupportedOperationException.class); + channelManager.isConnected(topic); + + channelManager.unsubscribe(topic); + + Optional result = channelManager.getAvailableMessageCount(topic); + + assertEquals(Optional.empty(), result); + } } \ No newline at end of file diff --git a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java index 2fe8bc0a..5b7b659e 100644 --- a/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java +++ b/core/src/test/java/io/github/tcdl/msb/ConsumerTest.java @@ -205,6 +205,16 @@ public void testMessageCount() { assertEquals(answer, result); } + @Test + public void testIsConsumerConnected() { + Consumer consumer = new Consumer(adapterMock, messageHandlerInvokerMock, TOPIC, messageHandlerResolverMock, msbConfMock, clock, validator, messageMapper); + Optional answer = Optional.of(true); + when(adapterMock.isConnected()).thenReturn(answer); + Optional result = consumer.isConnected(); + verify(adapterMock, times(1)).isConnected(); + assertEquals(answer, result); + } + @Test public void testHandleRawMessageConsumeFromTopicSkipValidation() { MsbConfig msbConf = spy(TestUtils.createMsbConfigurations()); diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java index c0d6544e..73ac344d 100644 --- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java +++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java @@ -9,6 +9,7 @@ import io.github.tcdl.msb.api.message.Message; import io.github.tcdl.msb.api.message.payload.RestPayload; import io.github.tcdl.msb.api.metrics.Gauge; +import io.github.tcdl.msb.api.metrics.MetricSet; import io.github.tcdl.msb.support.TestUtils; import org.junit.Before; import org.junit.Test; @@ -19,8 +20,7 @@ import java.util.Optional; import java.util.Set; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; @@ -82,7 +82,7 @@ public void testResponderServerProcessPayloadSuccess() throws Exception { } @Test - public void testResponderServerAvailableMessageCount() { + public void testResponderServerMetrics() { ResponderServer.RequestHandler, Object, Map>> handler = (request, responderContext) -> {}; ResponderOptions responderOptions = new ResponderOptions.Builder().withBindingKeys(Collections.emptySet()).withMessageTemplate(messageTemplate).build(); @@ -91,17 +91,24 @@ public void testResponderServerAvailableMessageCount() { ChannelManager spyChannelManager = spy(msbContext.getChannelManager()); when(spyMsbContext.getChannelManager()).thenReturn(spyChannelManager); when(spyChannelManager.getAvailableMessageCount(anyString())).thenReturn(Optional.of(666L)); + when(spyChannelManager.isConnected(anyString())).thenReturn(Optional.of(true)); ResponderServer responderServer = ResponderServerImpl.create(TOPIC,responderOptions, spyMsbContext, handler, null, new TypeReference, Object, Map>>() {}) .listen(); - Gauge availableMessageCount = (Gauge) responderServer.getMetrics().getMetric("availableMessageCount"); - assertEquals(666L, availableMessageCount.getValue()); + MetricSet metricSet = responderServer.getMetrics(); + Gauge availableMessageCount = (Gauge) metricSet.getMetric("availableMessageCount"); + Gauge isConsumerConnected = (Gauge) metricSet.getMetric("consumerConnected"); + + assertEquals(666L, availableMessageCount.getValue().longValue()); + assertTrue(isConsumerConnected.getValue()); + verify(spyChannelManager, times(1)).subscribe(eq(TOPIC), any(ResponderOptions.class), any(MessageHandler.class)); verify(spyChannelManager, times(1)).getAvailableMessageCount(TOPIC); + verify(spyChannelManager, times(1)).isConnected(TOPIC); } @Test(expected = NullPointerException.class) diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java index 09a978d3..3ed36712 100644 --- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java +++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbConsumerAdapter.java @@ -36,6 +36,11 @@ public Optional messageCount() { throw new UnsupportedOperationException("This method is not implemented in this test class."); } + @Override + public Optional isConnected() { + throw new UnsupportedOperationException("This method is not implemented in this test class."); + } + public void pushTestMessage(String jsonMessage) { AcknowledgementHandlerInternal ackHandler = mock(AcknowledgementHandlerInternal.class); rawMessageHandlers.forEach((handler)-> handler.onMessage(jsonMessage, ackHandler)); From 910f61a9b4183f5699317fdddf2eb38135343981 Mon Sep 17 00:00:00 2001 From: rdro-tc Date: Mon, 2 Apr 2018 11:48:53 +0300 Subject: [PATCH 208/226] POC: consumer status --- .../src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java b/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java index 5ee147cf..8cc4e6cf 100644 --- a/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java +++ b/core/src/main/java/io/github/tcdl/msb/api/metrics/MetricSet.java @@ -13,7 +13,7 @@ public interface MetricSet extends Metric { String MESSAGE_COUNT_METRIC = "availableMessageCount"; /** - * {@value #CONSUMER_CONNECTED_METRIC} metric key for the consumer status {@link Gauge} of {@link Long} type + * {@value #CONSUMER_CONNECTED_METRIC} metric key for the consumer status {@link Gauge} of {@link Boolean} type */ String CONSUMER_CONNECTED_METRIC = "consumerConnected"; From c12dcafaed2f3aae77cc2a22eeb9a3be0f6e5ec9 Mon Sep 17 00:00:00 2001 From: bohdan Date: Tue, 3 Apr 2018 13:22:18 +0300 Subject: [PATCH 209/226] Update amqp-client version --- .../msb/adapters/amqp/AmqpAdapterFactory.java | 33 +++++++++++-------- pom.xml | 2 +- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java index 50766041..1752cf75 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java @@ -3,10 +3,15 @@ import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.Recoverable; +import com.rabbitmq.client.RecoveryListener; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import io.github.tcdl.msb.adapters.AdapterFactory; -import io.github.tcdl.msb.api.*; +import io.github.tcdl.msb.api.AmqpRequestOptions; +import io.github.tcdl.msb.api.AmqpResponderOptions; +import io.github.tcdl.msb.api.ExchangeType; +import io.github.tcdl.msb.api.RequestOptions; +import io.github.tcdl.msb.api.ResponderOptions; import io.github.tcdl.msb.api.exception.AdapterCreationException; import io.github.tcdl.msb.api.exception.ChannelException; import io.github.tcdl.msb.api.exception.ConfigurationException; @@ -35,6 +40,7 @@ public class AmqpAdapterFactory implements AdapterFactory { * @throws ChannelException if an error is encountered during connecting to broker * @throws ConfigurationException if provided configuration is broken */ + @Override public void init(MsbConfig msbConfig) { amqpBrokerConfig = createAmqpBrokerConfig(msbConfig); LOG.debug("MSB AMQP Broker configuration {}", amqpBrokerConfig); @@ -114,21 +120,13 @@ protected ConnectionFactory createConnectionFactory(AmqpBrokerConfig adapterConf ConnectionFactory connectionFactory = createConnectionFactory(); connectionFactory.setHost(host); connectionFactory.setPort(port); - connectionFactory.setAutomaticRecoveryEnabled(true); - connectionFactory.setTopologyRecoveryEnabled(true); connectionFactory.setNetworkRecoveryInterval(adapterConfig.getNetworkRecoveryIntervalMs()); connectionFactory.setRequestedHeartbeat(adapterConfig.getHeartbeatIntervalSec()); connectionFactory.setExceptionHandler(new AmqpExceptionHandler()); - if (username.isPresent()) { - connectionFactory.setUsername(username.get()); - } - if (password.isPresent()) { - connectionFactory.setPassword(password.get()); - } - if (virtualHost.isPresent()) { - connectionFactory.setVirtualHost(virtualHost.get()); - } + username.ifPresent(connectionFactory::setUsername); + password.ifPresent(connectionFactory::setPassword); + virtualHost.ifPresent(connectionFactory::setVirtualHost); try { if (adapterConfig.useSSL()) { @@ -163,7 +161,16 @@ protected Connection createConnection(ConnectionFactory connectionFactory) { Connection connection = connectionFactory.newConnection(); if (connection instanceof Recoverable) { // This cast is possible for connections created by a factory that supports auto-recovery - ((Recoverable) connection).addRecoveryListener(recoverable -> LOG.info("AMQP connection recovered.")); + ((Recoverable) connection).addRecoveryListener(new RecoveryListener() { + @Override + public void handleRecovery(Recoverable recoverable) { + LOG.info("AMQP connection recovered."); + } + @Override + public void handleRecoveryStarted(Recoverable recoverable) { + LOG.info("AMQP connection recovery started."); + } + }); } LOG.info("AMQP connection opened."); return connection; diff --git a/pom.xml b/pom.xml index 1143e0c0..ad3de234 100644 --- a/pom.xml +++ b/pom.xml @@ -124,7 +124,7 @@ com.rabbitmq amqp-client - 3.6.0 + 5.2.0 com.googlecode.junit-toolbox From 55fba096a3dee6770e981b1ebd445b90ece894c6 Mon Sep 17 00:00:00 2001 From: bohdan Date: Tue, 3 Apr 2018 15:15:09 +0300 Subject: [PATCH 210/226] Revert default recovery options (for compatibility with older amqp-client versions) --- .../io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java index 1752cf75..ecc846fb 100644 --- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java +++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java @@ -120,6 +120,8 @@ protected ConnectionFactory createConnectionFactory(AmqpBrokerConfig adapterConf ConnectionFactory connectionFactory = createConnectionFactory(); connectionFactory.setHost(host); connectionFactory.setPort(port); + connectionFactory.setAutomaticRecoveryEnabled(true); + connectionFactory.setTopologyRecoveryEnabled(true); connectionFactory.setNetworkRecoveryInterval(adapterConfig.getNetworkRecoveryIntervalMs()); connectionFactory.setRequestedHeartbeat(adapterConfig.getHeartbeatIntervalSec()); connectionFactory.setExceptionHandler(new AmqpExceptionHandler()); From 2e7ea0fb50d303c778b60ae481f10775cf41ed64 Mon Sep 17 00:00:00 2001 From: Yuriy Savchuk Date: Thu, 3 May 2018 18:01:53 +0300 Subject: [PATCH 211/226] 1.6.5 release preparation --- release-notes.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/release-notes.html b/release-notes.html index f0bf499f..9860f644 100644 --- a/release-notes.html +++ b/release-notes.html @@ -6,11 +6,13 @@

Welcome to MSB-Java version 1.6.5

-

March 30, 2018

+

May 3, 2018

 Changes in MSB-Java version 1.6.5:
-   - Upgrade msb-spring-boot-starter to Spring Boot 2 release
+   - Updated AMQP client version from 3.6.0 to 5.2.0; 
+   - Added check consumer connection isConnected() method;
+   - Upgraded msb-spring-boot-starter to Spring Boot 2 release;
 
 Changes in MSB-Java version 1.6.4:
    - Exposed Message Count Metric for ResponderServer

From 38d7c381979c374c012b0a813f59905474a6878f Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Thu, 3 May 2018 18:13:10 +0300
Subject: [PATCH 212/226] [maven-release-plugin] prepare release msb-java-1.6.5

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 cli/pom.xml                 | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 1ffc22e7..7ddfae2a 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5-SNAPSHOT
+        1.6.5
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.5
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 7f9ee86d..f116e8e5 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5-SNAPSHOT
+        1.6.5
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.5
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 5bd84262..414cbb03 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5-SNAPSHOT
+        1.6.5
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.5
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index d1495bec..0151c68c 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5-SNAPSHOT
+        1.6.5
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.5
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index fd8536cb..cea58b7a 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5-SNAPSHOT
+        1.6.5
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.5
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 493b6331..e69afd20 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5-SNAPSHOT
+        1.6.5
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.5
     
 
     
diff --git a/pom.xml b/pom.xml
index ad3de234..b132aa3b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.5-SNAPSHOT
+    1.6.5
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.5
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index ce00726e..14b14c56 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.5-SNAPSHOT
+		1.6.5
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		HEAD
+		msb-java-1.6.5
 	
 
 	

From 8d60e2f9ad9b9fdbf0a13e7ec0b98d64375cf425 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Thu, 3 May 2018 18:13:22 +0300
Subject: [PATCH 213/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 cli/pom.xml                 | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index 7ddfae2a..ae0169b9 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5
+        1.6.6-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.5
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index f116e8e5..f716f1e0 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5
+        1.6.6-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.5
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 414cbb03..5a1dbbfd 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5
+        1.6.6-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.5
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 0151c68c..d3e91a10 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5
+        1.6.6-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.5
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index cea58b7a..4fbef726 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5
+        1.6.6-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.5
+        HEAD
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index e69afd20..922bdc39 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.5
+        1.6.6-SNAPSHOT
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.5
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index b132aa3b..e1c5b7c3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.5
+    1.6.6-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.5
+        HEAD
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index 14b14c56..a4ed3f61 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.5
+		1.6.6-SNAPSHOT
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		msb-java-1.6.5
+		HEAD
 	
 
 	

From 0aedc55b574760ea48fd30119da862e5c6064155 Mon Sep 17 00:00:00 2001
From: bohdan 
Date: Tue, 12 Jun 2018 15:22:13 +0300
Subject: [PATCH 214/226] Attempt to log unhandled errors during message
 processing

---
 .../io/github/tcdl/msb/threading/MessageProcessingTask.java    | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java b/core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java
index ccc69f0b..8b56a5d4 100644
--- a/core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java
+++ b/core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java
@@ -48,6 +48,9 @@ public void run() {
         } catch (Exception e) {
             LOG.error("[correlation id: {}] Failed to process message", message.getCorrelationId(), e);
             ackHandler.autoRetry();
+        } catch (Error error) {
+            LOG.error("[correlation id: {}] Failed to process message", message.getCorrelationId(), error);
+            throw error;
         } finally {
             if(mdcLogCopy) {
                 MDC.clear();

From 038dd8c8c935abe561231afbb79015bedffd9862 Mon Sep 17 00:00:00 2001
From: Jacobo Abengozar 
Date: Thu, 6 Sep 2018 11:50:57 +0200
Subject: [PATCH 215/226] Disable MSB-Java using property

- true or not present means enable (back compatibility)
- explicit false means disable
- Updated documentation
- Added test
---
 doc/MSB.md                                    |  4 +++
 .../MsbConfigAutoConfiguration.java           |  5 ++--
 .../MsbContextAutoConfiguration.java          |  2 ++
 .../MsbAutoConfigurationDisableTest.java      | 27 +++++++++++++++++++
 4 files changed, 36 insertions(+), 2 deletions(-)
 create mode 100644 spring-boot-starter/src/test/java/io/github/tcdl/msb/autoconfigure/MsbAutoConfigurationDisableTest.java

diff --git a/doc/MSB.md b/doc/MSB.md
index 6c7bbdba..26f41eb5 100644
--- a/doc/MSB.md
+++ b/doc/MSB.md
@@ -285,6 +285,10 @@ All configuration files use _key-value pair_ structure.
 - [application.conf](/acceptance/src/main/resources/application.conf) - overrides values from reference.conf
 
 ### Description of MSB configuration fields
+
+There is a feature flag for enable/disable MSB:
+- `msb-config.enabled` true or missing value means enabled. false for disable whole MSB. It's very useful for testing and for enable/disable the msb capabilities depending of environments (f.e. as feature flag if the functionality is not desirable and/or not tested yet).
+
 Service details section describes microservice parameters.
 
 - `name ` – microservice name. All running instances of the same microservice must have the same name. Mandatory, has no default value.
diff --git a/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java
index b4f9c526..4c8866f5 100644
--- a/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java
+++ b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbConfigAutoConfiguration.java
@@ -8,10 +8,12 @@
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+@ConditionalOnProperty(name = "msb-config.enabled", havingValue = "true", matchIfMissing = true)
 @Configuration
 @AutoConfigureBefore(MsbContextAutoConfiguration.class)
 @EnableConfigurationProperties(MsbProperties.class)
@@ -20,9 +22,8 @@ public class MsbConfigAutoConfiguration {
     public static final String DEFAULT_VERSION = "1.0.0";
     public static final String DEFAULT_APP_NAME = Utils.generateId();
     public static final String DEFAULT_ADAPTER_FACTORY = "io.github.tcdl.msb.adapters.amqp.AmqpAdapterFactory";
-    private static final boolean DEFAULT_BROKER_DURABLE = true;
     public static final int DEFAULT_TIMER_THREAD_POOL_SIZE = 2;
-
+    private static final boolean DEFAULT_BROKER_DURABLE = true;
     @Autowired
     MsbProperties msbProperties;
 
diff --git a/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java
index 1e135320..504600b4 100644
--- a/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java
+++ b/spring-boot-starter/src/main/java/io/github/tcdl/msb/autoconfigure/MsbContextAutoConfiguration.java
@@ -7,9 +7,11 @@
 import io.github.tcdl.msb.threading.MessageGroupStrategy;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+@ConditionalOnProperty(name = "msb-config.enabled", havingValue = "true", matchIfMissing = true)
 @Configuration
 public class MsbContextAutoConfiguration {
 
diff --git a/spring-boot-starter/src/test/java/io/github/tcdl/msb/autoconfigure/MsbAutoConfigurationDisableTest.java b/spring-boot-starter/src/test/java/io/github/tcdl/msb/autoconfigure/MsbAutoConfigurationDisableTest.java
new file mode 100644
index 00000000..a8088a87
--- /dev/null
+++ b/spring-boot-starter/src/test/java/io/github/tcdl/msb/autoconfigure/MsbAutoConfigurationDisableTest.java
@@ -0,0 +1,27 @@
+package io.github.tcdl.msb.autoconfigure;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.Assert.assertTrue;
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = {MsbConfigAutoConfiguration.class, MsbContextAutoConfiguration.class})
+@TestPropertySource(properties = "msb-config.enabled=false")
+public class MsbAutoConfigurationDisableTest {
+
+    @Autowired
+    private ApplicationContext context;
+
+    @Test
+    public void shouldDisableAutoConfigurationByFeatureFlag() {
+        assertTrue(context.getBeansOfType(MsbConfigAutoConfiguration.class).isEmpty());
+        assertTrue(context.getBeansOfType(MsbContextAutoConfiguration.class).isEmpty());
+    }
+
+}

From 2262cc7980ae7bdbf14ff6421c6633d0caea6774 Mon Sep 17 00:00:00 2001
From: Bohdan Dzoba 
Date: Thu, 18 Oct 2018 13:34:30 +0300
Subject: [PATCH 216/226] IN-5774 Auto-retry after all errors if
 auto-acknowledgement is enabled

---
 .../msb/threading/MessageProcessingTask.java  |  5 +----
 .../threading}/MessageProcessingTaskTest.java | 21 +++++++++++--------
 2 files changed, 13 insertions(+), 13 deletions(-)
 rename {amqp/src/test/java/io/github/tcdl/msb/adapters/amqp => core/src/test/java/io/github/tcdl/msb/threading}/MessageProcessingTaskTest.java (87%)

diff --git a/core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java b/core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java
index 8b56a5d4..f1a38604 100644
--- a/core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java
+++ b/core/src/main/java/io/github/tcdl/msb/threading/MessageProcessingTask.java
@@ -45,12 +45,9 @@ public void run() {
             messageHandler.handleMessage(message, ackHandler);
             LOG.debug("[correlation id: {}] Message has been processed", message.getCorrelationId());
             ackHandler.autoConfirm();
-        } catch (Exception e) {
+        } catch (Throwable e) {
             LOG.error("[correlation id: {}] Failed to process message", message.getCorrelationId(), e);
             ackHandler.autoRetry();
-        } catch (Error error) {
-            LOG.error("[correlation id: {}] Failed to process message", message.getCorrelationId(), error);
-            throw error;
         } finally {
             if(mdcLogCopy) {
                 MDC.clear();
diff --git a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/MessageProcessingTaskTest.java b/core/src/test/java/io/github/tcdl/msb/threading/MessageProcessingTaskTest.java
similarity index 87%
rename from amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/MessageProcessingTaskTest.java
rename to core/src/test/java/io/github/tcdl/msb/threading/MessageProcessingTaskTest.java
index 0865ae65..fcad0eae 100644
--- a/amqp/src/test/java/io/github/tcdl/msb/adapters/amqp/MessageProcessingTaskTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/threading/MessageProcessingTaskTest.java
@@ -1,4 +1,4 @@
-package io.github.tcdl.msb.adapters.amqp;
+package io.github.tcdl.msb.threading;
 
 import static org.junit.Assert.*;
 import static org.mockito.Matchers.any;
@@ -11,14 +11,13 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.concurrent.*;
+import java.util.function.Supplier;
 
 import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl;
 import io.github.tcdl.msb.api.message.Message;
-import io.github.tcdl.msb.threading.MessageProcessingTask;
 import org.junit.Before;
 import org.junit.Test;
 
-import com.rabbitmq.client.Channel;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
@@ -30,8 +29,6 @@ public class MessageProcessingTaskTest {
     private final String MDC_VALUE = "any";
 
     private Message message;
-    @Mock
-    private Channel mockChannel;
 
     @Mock
     private MessageHandler mockMessageHandler;
@@ -57,13 +54,19 @@ public void testMessageProcessing() throws IOException {
 
     @Test
     public void testExceptionDuringProcessing() {
-        doThrow(new RuntimeException()).when(mockMessageHandler).handleMessage(any(), any());
+        testThrowableDuringProcessing(RuntimeException::new);
+    }
+
+    @Test
+    public void testErrorDuringProcessing() {
+        testThrowableDuringProcessing(AssertionError::new);
+    }
+
+    private  void testThrowableDuringProcessing(Supplier throwable) {
+        doThrow(throwable.get()).when(mockMessageHandler).handleMessage(any(), any());
 
         try {
             task.run();
-            // Verify that AMQP ack has not been sent
-            verifyNoMoreInteractions(mockChannel);
-
             verify(mockAcknowledgementHandler, times(1)).autoRetry();
             verifyNoMoreInteractions(mockAcknowledgementHandler);
         } catch (Exception e) {

From a3bc9983b6080aae7b04f7d9ecba968e1a41004c Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Thu, 1 Nov 2018 18:07:33 +0200
Subject: [PATCH 217/226] Used amqp-client 5.4.0

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index e1c5b7c3..c0188718 100644
--- a/pom.xml
+++ b/pom.xml
@@ -124,7 +124,7 @@
             
                 com.rabbitmq
                 amqp-client
-                5.2.0
+                5.4.0
             
             
                 com.googlecode.junit-toolbox

From b26ad17e21e67089434a6ffe95d21484dc9a38fc Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Tue, 6 Nov 2018 18:12:49 +0200
Subject: [PATCH 218/226] 1.6.6 release preparation

---
 release-notes.html | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/release-notes.html b/release-notes.html
index 9860f644..0bc1a1cd 100644
--- a/release-notes.html
+++ b/release-notes.html
@@ -4,11 +4,16 @@
 
 
 
-

Welcome to MSB-Java version 1.6.5

+

Welcome to MSB-Java version 1.6.6

-

May 3, 2018

+

November 7, 2018

+Changes in MSB-Java version 1.6.6:
+   - Updated AMQP client version from 5.2.0 to 5.4.0; 
+   - Updated java.lang.Error handling with auto-retry if auto-acknowledgement is enabled;
+   - Added 'msb-config.enabled' disable msb-java using property for testing purposes;
+
 Changes in MSB-Java version 1.6.5:
    - Updated AMQP client version from 3.6.0 to 5.2.0; 
    - Added check consumer connection isConnected() method;

From e756e3672ce360b56e9397f661cf6965981e5e95 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Tue, 6 Nov 2018 18:16:05 +0200
Subject: [PATCH 219/226] [maven-release-plugin] prepare release msb-java-1.6.6

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 cli/pom.xml                 | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index ae0169b9..f9657291 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6-SNAPSHOT
+        1.6.6
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.6
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index f716f1e0..8bec4a3a 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6-SNAPSHOT
+        1.6.6
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.6
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 5a1dbbfd..0b629175 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6-SNAPSHOT
+        1.6.6
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.6
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index d3e91a10..8b3594c0 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6-SNAPSHOT
+        1.6.6
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.6
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 4fbef726..1c0cdb22 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6-SNAPSHOT
+        1.6.6
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.6
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index 922bdc39..f589619a 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6-SNAPSHOT
+        1.6.6
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.6
     
 
     
diff --git a/pom.xml b/pom.xml
index c0188718..ca3353b2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.6-SNAPSHOT
+    1.6.6
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        HEAD
+        msb-java-1.6.6
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index a4ed3f61..07a67c3c 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.6-SNAPSHOT
+		1.6.6
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		HEAD
+		msb-java-1.6.6
 	
 
 	

From 647b0906677c16df9a36d407aceffd7f55fdc02c Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Tue, 6 Nov 2018 18:16:17 +0200
Subject: [PATCH 220/226] [maven-release-plugin] prepare for next development
 iteration

---
 acceptance/pom.xml          | 4 ++--
 amqp/pom.xml                | 4 ++--
 cli/pom.xml                 | 4 ++--
 core/pom.xml                | 4 ++--
 examples/pom.xml            | 4 ++--
 jmeter/pom.xml              | 4 ++--
 pom.xml                     | 4 ++--
 spring-boot-starter/pom.xml | 4 ++--
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/acceptance/pom.xml b/acceptance/pom.xml
index f9657291..c5296d62 100644
--- a/acceptance/pom.xml
+++ b/acceptance/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6
+        1.6.7-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.6
+        HEAD
     
     
         tcdl
diff --git a/amqp/pom.xml b/amqp/pom.xml
index 8bec4a3a..f76a41a5 100644
--- a/amqp/pom.xml
+++ b/amqp/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6
+        1.6.7-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.6
+        HEAD
     
     
         tcdl
diff --git a/cli/pom.xml b/cli/pom.xml
index 0b629175..3e46d823 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6
+        1.6.7-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.6
+        HEAD
     
     
         tcdl
diff --git a/core/pom.xml b/core/pom.xml
index 8b3594c0..8e633e39 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6
+        1.6.7-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.6
+        HEAD
     
     
         tcdl
diff --git a/examples/pom.xml b/examples/pom.xml
index 1c0cdb22..e4a9e475 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6
+        1.6.7-SNAPSHOT
         ../pom.xml
     
     4.0.0
@@ -15,7 +15,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.6
+        HEAD
     
 
     
diff --git a/jmeter/pom.xml b/jmeter/pom.xml
index f589619a..75228d70 100644
--- a/jmeter/pom.xml
+++ b/jmeter/pom.xml
@@ -6,7 +6,7 @@
     
         io.github.tcdl.msb
         msb-java
-        1.6.6
+        1.6.7-SNAPSHOT
         ../pom.xml
     
 
@@ -17,7 +17,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.6
+        HEAD
     
 
     
diff --git a/pom.xml b/pom.xml
index ca3353b2..53b950df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     4.0.0
     io.github.tcdl.msb
     msb-java
-    1.6.6
+    1.6.7-SNAPSHOT
     msb java
     msb java
     pom
@@ -11,7 +11,7 @@
         scm:git:https://github.com/tcdl/msb-java.git
         scm:git:git@github.com:tcdl/msb-java.git
         https://github.com/tcdl/msb-java
-        msb-java-1.6.6
+        HEAD
     
     
         
diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml
index 07a67c3c..648bb27f 100644
--- a/spring-boot-starter/pom.xml
+++ b/spring-boot-starter/pom.xml
@@ -3,7 +3,7 @@
 	
 		io.github.tcdl.msb
 		msb-java
-		1.6.6
+		1.6.7-SNAPSHOT
 		../pom.xml
 	
 	4.0.0
@@ -15,7 +15,7 @@
 		scm:git:https://github.com/tcdl/msb-java.git
 		scm:git:git@github.com:tcdl/msb-java.git
 		https://github.com/tcdl/msb-java
-		msb-java-1.6.6
+		HEAD
 	
 
 	

From fce791544afe1c6ee18aeca388edcbf3e707210e Mon Sep 17 00:00:00 2001
From: Bohdan Dzoba 
Date: Wed, 14 Nov 2018 18:03:31 +0100
Subject: [PATCH 221/226] Allow to override messageHandlerInvoker

---
 .../src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
index 1166ba66..acc0e427 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
@@ -142,7 +142,7 @@ public void run() {
         return msbContext;
     }
 
-    private MessageHandlerInvoker createMessageHandlerInvoker(AdapterFactory adapterFactory, MsbConfig msbConfig) {
+    protected MessageHandlerInvoker createMessageHandlerInvoker(AdapterFactory adapterFactory, MsbConfig msbConfig) {
         ConsumerExecutorFactory consumerExecutorFactory = new ConsumerExecutorFactoryImpl();
 
         MessageHandlerInvoker consumerMessageHandlerInvoker;

From 72251fbd04edf8ef703c9482143a60c0883e0ed8 Mon Sep 17 00:00:00 2001
From: Yuriy Savchuk 
Date: Tue, 6 Nov 2018 18:16:17 +0200
Subject: [PATCH 222/226] Add setter for messageHandlerInvoker

---
 .../tcdl/msb/api/MsbContextBuilder.java       | 38 ++++++++---
 .../ConsumerExecutorFactoryImpl.java          | 14 +++--
 ...pedExecutorBasedMessageHandlerInvoker.java | 63 -------------------
 .../GroupedMessageHandlerInvoker.java         | 46 ++++++++++++++
 .../msb/threading/MessageGroupStrategy.java   |  2 +-
 .../MessageHandlerInvokerFactory.java         | 22 +++++++
 .../MessageHandlerInvokerFactoryImpl.java     | 20 ++++++
 .../ThreadPoolMessageHandlerInvoker.java      | 11 ++--
 .../ConsumerExecutorFactoryTest.java          | 26 +++-----
 ... => GroupedMessageHandlerInvokerTest.java} | 28 ++++++---
 10 files changed, 162 insertions(+), 108 deletions(-)
 delete mode 100644 core/src/main/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvoker.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/threading/GroupedMessageHandlerInvoker.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactory.java
 create mode 100644 core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactoryImpl.java
 rename core/src/test/java/io/github/tcdl/msb/threading/{GroupedExecutorBasedMessageHandlerInvokerTest.java => GroupedMessageHandlerInvokerTest.java} (84%)

diff --git a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
index acc0e427..4f386b58 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
@@ -19,7 +19,13 @@
 import io.github.tcdl.msb.impl.ObjectFactoryImpl;
 import io.github.tcdl.msb.message.MessageFactory;
 import io.github.tcdl.msb.support.JsonValidator;
-import io.github.tcdl.msb.threading.*;
+import io.github.tcdl.msb.threading.ConsumerExecutorFactoryImpl;
+import io.github.tcdl.msb.threading.DirectInvocationCapableInvoker;
+import io.github.tcdl.msb.threading.DirectMessageHandlerInvoker;
+import io.github.tcdl.msb.threading.MessageGroupStrategy;
+import io.github.tcdl.msb.threading.MessageHandlerInvoker;
+import io.github.tcdl.msb.threading.MessageHandlerInvokerFactory;
+import io.github.tcdl.msb.threading.MessageHandlerInvokerFactoryImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,6 +44,7 @@ public class MsbContextBuilder {
     private boolean enableShutdownHook;
     private ObjectMapper payloadMapper = createMessageEnvelopeMapper();
     private MessageGroupStrategy messageGroupStrategy;
+    private MessageHandlerInvokerFactory messageHandlerInvokerFactory;
 
     public MsbContextBuilder() {
         super();
@@ -92,6 +99,17 @@ public MsbContextBuilder withPayloadMapper(ObjectMapper payloadMapper) {
         return this;
     }
 
+    /**
+     * Specifies message handler invoker factory
+     *
+     * @param messageHandlerInvokerFactory if not provided default factory will be used
+     * @return MsbContextBuilder
+     */
+    public MsbContextBuilder withMessageHandlerInvokerFactory(MessageHandlerInvokerFactory messageHandlerInvokerFactory) {
+        this.messageHandlerInvokerFactory = messageHandlerInvokerFactory;
+        return this;
+    }
+
     /**
      * Create implementation of {@link MsbContext}
      * Can be initialized with configuration from reference.conf (property file inside MSB library) or application.conf,
@@ -111,6 +129,9 @@ public MsbContext build() {
             }
             msbConfig = new MsbConfig(config);
         }
+        if (messageHandlerInvokerFactory == null) {
+            messageHandlerInvokerFactory = new MessageHandlerInvokerFactoryImpl(new ConsumerExecutorFactoryImpl());
+        }
         ObjectMapper messageEnvelopeMapper = createMessageEnvelopeMapper();
 
         AdapterFactory adapterFactory = new AdapterFactoryLoader(msbConfig).getAdapterFactory();
@@ -142,21 +163,20 @@ public void run() {
         return msbContext;
     }
 
-    protected MessageHandlerInvoker createMessageHandlerInvoker(AdapterFactory adapterFactory, MsbConfig msbConfig) {
-        ConsumerExecutorFactory consumerExecutorFactory = new ConsumerExecutorFactoryImpl();
-
+    private MessageHandlerInvoker createMessageHandlerInvoker(AdapterFactory adapterFactory, MsbConfig msbConfig) {
         MessageHandlerInvoker consumerMessageHandlerInvoker;
         if (adapterFactory.isUseMsbThreadingModel()) {
             if (messageGroupStrategy == null) {
-                consumerMessageHandlerInvoker = new ThreadPoolMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(),
-                        consumerExecutorFactory);
+                consumerMessageHandlerInvoker = messageHandlerInvokerFactory.createExecutorBasedHandlerInvoker(
+                        msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity());
             } else {
-                consumerMessageHandlerInvoker = new GroupedExecutorBasedMessageHandlerInvoker(msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity(),
-                        consumerExecutorFactory,
+                consumerMessageHandlerInvoker = messageHandlerInvokerFactory.createGroupedExecutorBasedHandlerInvoker(
+                        msbConfig.getConsumerThreadPoolSize(),
+                        msbConfig.getConsumerThreadPoolQueueCapacity(),
                         messageGroupStrategy);
             }
         } else {
-            consumerMessageHandlerInvoker = new DirectMessageHandlerInvoker();
+            consumerMessageHandlerInvoker = messageHandlerInvokerFactory.createDirectHandlerInvoker();
         }
         return new DirectInvocationCapableInvoker(consumerMessageHandlerInvoker, new DirectMessageHandlerInvoker());
     }
diff --git a/core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryImpl.java
index 91da84e5..d23b6259 100644
--- a/core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryImpl.java
@@ -2,18 +2,22 @@
 
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 
-import java.util.concurrent.*;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 
 public class ConsumerExecutorFactoryImpl implements ConsumerExecutorFactory {
 
     protected static final int QUEUE_SIZE_UNLIMITED = -1;
+    private final BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
+            .namingPattern("msb-consumer-thread-%d")
+            .build();
 
     @Override
     public ExecutorService createConsumerThreadPool(int numberOfThreads, int queueCapacity) {
-        BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
-                .namingPattern("msb-consumer-thread-%d")
-                .build();
-
         BlockingQueue queue;
         if (queueCapacity == QUEUE_SIZE_UNLIMITED) {
             queue = new LinkedBlockingQueue<>();
diff --git a/core/src/main/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvoker.java b/core/src/main/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvoker.java
deleted file mode 100644
index a7e90501..00000000
--- a/core/src/main/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvoker.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package io.github.tcdl.msb.threading;
-
-import io.github.tcdl.msb.api.message.Message;
-import io.github.tcdl.msb.support.Utils;
-import org.apache.commons.lang3.RandomUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.concurrent.ExecutorService;
-import java.util.stream.IntStream;
-
-/**
- * This {@link MessageHandlerInvoker} implementation gives an ability to execute {@link io.github.tcdl.msb.MessageHandler}
- * sequentially for messages with the same "groupId" (resolved by {@link MessageGroupStrategy} provided) while
- * messages with different "groupId" could be processed in parallel.
- */
-public class GroupedExecutorBasedMessageHandlerInvoker extends ExecutorBasedMessageHandlerInvoker {
-
-    private static final Logger LOG = LoggerFactory.getLogger(GroupedExecutorBasedMessageHandlerInvoker.class);
-
-    private final ExecutorService[] executors;
-
-    private final MessageGroupStrategy messageGroupStrategy;
-    private final int numberOfThreads;
-
-    public GroupedExecutorBasedMessageHandlerInvoker(int numberOfThreads, int queueCapacity,
-            ConsumerExecutorFactory consumerExecutorFactory, MessageGroupStrategy messageGroupStrategy) {
-        super(consumerExecutorFactory);
-        this.messageGroupStrategy = messageGroupStrategy;
-        this.numberOfThreads = numberOfThreads;
-
-        executors = new ExecutorService[numberOfThreads];
-
-        IntStream
-                .range(0, numberOfThreads)
-                .forEach(i -> executors[i] =
-                                consumerExecutorFactory.createConsumerThreadPool(1, queueCapacity));
-    }
-
-    @Override
-    protected void doSubmitTask(MessageProcessingTask task, Message message) {
-        int executorKey = getExecutorKey(message);
-        executors[executorKey].submit(task);
-    }
-
-    private int getExecutorKey(Message message) {
-        Optional messageGroupId = messageGroupStrategy.getMessageGroupId(message);
-        if(messageGroupId.isPresent()) {
-            return Math.abs(messageGroupId.get() % numberOfThreads);
-        } else {
-            return RandomUtils.nextInt(0, numberOfThreads);
-        }
-    }
-
-    @Override
-    public void shutdown() {
-        Arrays
-                .stream(executors)
-                .forEach(executor -> Utils.gracefulShutdown(executor, "consumer"));
-    }
-}
diff --git a/core/src/main/java/io/github/tcdl/msb/threading/GroupedMessageHandlerInvoker.java b/core/src/main/java/io/github/tcdl/msb/threading/GroupedMessageHandlerInvoker.java
new file mode 100644
index 00000000..0e76a0bb
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/threading/GroupedMessageHandlerInvoker.java
@@ -0,0 +1,46 @@
+package io.github.tcdl.msb.threading;
+
+import io.github.tcdl.msb.MessageHandler;
+import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.api.message.Message;
+import org.apache.commons.lang3.RandomUtils;
+
+import java.util.List;
+
+/**
+ * This {@link MessageHandlerInvoker} implementation delegates execution of {@link io.github.tcdl.msb.MessageHandler}
+ * to one of the provided invokers. Messages with the same group (resolved by {@link MessageGroupStrategy} provided)
+ * will be processed by the same invoker.
+ *
+ * For example, this class can be used to process messages from the same group sequentially by providing a list of
+ * single-threaded invokers.
+ */
+public class GroupedMessageHandlerInvoker implements MessageHandlerInvoker {
+
+    private final MessageGroupStrategy messageGroupStrategy;
+    private final int numberOfInvokers;
+    private final List invokers;
+
+    public GroupedMessageHandlerInvoker(List invokers, MessageGroupStrategy messageGroupStrategy) {
+        this.messageGroupStrategy = messageGroupStrategy;
+        this.numberOfInvokers = invokers.size();
+        this.invokers = invokers;
+    }
+
+    private int getInvokerKey(Message message) {
+        return messageGroupStrategy.getMessageGroupId(message)
+                .map(integer -> Math.abs(integer % numberOfInvokers))
+                .orElseGet(() -> RandomUtils.nextInt(0, numberOfInvokers));
+    }
+
+    @Override
+    public void execute(MessageHandler messageHandler, Message message, AcknowledgementHandlerInternal acknowledgeHandler) {
+        int invokerKey = getInvokerKey(message);
+        invokers.get(invokerKey).execute(messageHandler, message, acknowledgeHandler);
+    }
+
+    @Override
+    public void shutdown() {
+        invokers.forEach(MessageHandlerInvoker::shutdown);
+    }
+}
diff --git a/core/src/main/java/io/github/tcdl/msb/threading/MessageGroupStrategy.java b/core/src/main/java/io/github/tcdl/msb/threading/MessageGroupStrategy.java
index d96ffd0d..c919715d 100644
--- a/core/src/main/java/io/github/tcdl/msb/threading/MessageGroupStrategy.java
+++ b/core/src/main/java/io/github/tcdl/msb/threading/MessageGroupStrategy.java
@@ -6,7 +6,7 @@
 
 /**
  * Implementations of this interface define a way to resolve a message group by a message. Messages
- * with the same message group will be executed by {@link GroupedExecutorBasedMessageHandlerInvoker}
+ * with the same message group will be executed by {@link GroupedMessageHandlerInvoker}
  * one after another (in a single-threaded mode)
  * while messages with different message groups could be executed in parallel.
  */
diff --git a/core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactory.java b/core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactory.java
new file mode 100644
index 00000000..6deccd7a
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactory.java
@@ -0,0 +1,22 @@
+package io.github.tcdl.msb.threading;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public interface MessageHandlerInvokerFactory {
+
+    MessageHandlerInvoker createDirectHandlerInvoker();
+
+    MessageHandlerInvoker createExecutorBasedHandlerInvoker(int numberOfThreads, int queueCapacity);
+
+    default MessageHandlerInvoker createGroupedExecutorBasedHandlerInvoker(
+            int numberOfThreads, int queueCapacity, MessageGroupStrategy messageGroupStrategy) {
+        List invokers = IntStream
+                .range(0, numberOfThreads)
+                .boxed()
+                .map(i -> createExecutorBasedHandlerInvoker(1, queueCapacity))
+                .collect(Collectors.toList());
+        return new GroupedMessageHandlerInvoker<>(invokers, messageGroupStrategy);
+    }
+}
diff --git a/core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactoryImpl.java b/core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactoryImpl.java
new file mode 100644
index 00000000..89238edd
--- /dev/null
+++ b/core/src/main/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactoryImpl.java
@@ -0,0 +1,20 @@
+package io.github.tcdl.msb.threading;
+
+public class MessageHandlerInvokerFactoryImpl implements MessageHandlerInvokerFactory {
+
+    private final ConsumerExecutorFactory consumerExecutorFactory;
+
+    public MessageHandlerInvokerFactoryImpl(ConsumerExecutorFactory consumerExecutorFactory) {
+        this.consumerExecutorFactory = consumerExecutorFactory;
+    }
+
+    @Override
+    public MessageHandlerInvoker createDirectHandlerInvoker() {
+        return new DirectMessageHandlerInvoker();
+    }
+
+    @Override
+    public MessageHandlerInvoker createExecutorBasedHandlerInvoker(int numberOfThreads, int queueCapacity) {
+        return new ThreadPoolMessageHandlerInvoker(numberOfThreads, queueCapacity, consumerExecutorFactory);
+    }
+}
diff --git a/core/src/main/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvoker.java b/core/src/main/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvoker.java
index 76a06987..03490839 100644
--- a/core/src/main/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvoker.java
+++ b/core/src/main/java/io/github/tcdl/msb/threading/ThreadPoolMessageHandlerInvoker.java
@@ -2,8 +2,6 @@
 
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.support.Utils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.concurrent.ExecutorService;
 
@@ -12,12 +10,10 @@
  * in a single thread pool with a configured number of threads. This approach is effective, but may lead
  * to concurrent issues when incoming messages order matters. When facing this kind of issues,
  * it is possible either to configure this class to work in a single-threaded mode,
- * or use {@link GroupedExecutorBasedMessageHandlerInvoker} instead.
+ * or use {@link GroupedMessageHandlerInvoker} instead.
  */
 public class ThreadPoolMessageHandlerInvoker extends ExecutorBasedMessageHandlerInvoker {
 
-    private static final Logger LOG = LoggerFactory.getLogger(ThreadPoolMessageHandlerInvoker.class);
-
     private final ExecutorService executor;
 
     public ThreadPoolMessageHandlerInvoker(int numberOfThreads, int queueCapacity, ConsumerExecutorFactory consumerExecutorFactory) {
@@ -32,7 +28,10 @@ protected void doSubmitTask(MessageProcessingTask task, Message message) {
 
     @Override
     public void shutdown() {
-        Utils.gracefulShutdown(executor, "consumer");
+        doShutdown(executor);
     }
 
+    protected void doShutdown(ExecutorService executor) {
+        Utils.gracefulShutdown(executor, "consumer");
+    }
 }
\ No newline at end of file
diff --git a/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java b/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java
index b3843188..d03e98e3 100644
--- a/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java
@@ -1,7 +1,9 @@
 package io.github.tcdl.msb.threading;
 
-import io.github.tcdl.msb.api.message.Message;
-import io.github.tcdl.msb.config.MsbConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
 
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
@@ -9,25 +11,17 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 
-import io.github.tcdl.msb.threading.ExecutorBasedMessageHandlerInvoker;
-import io.github.tcdl.msb.threading.MessageProcessingTask;
-import io.github.tcdl.msb.threading.ThreadPoolMessageHandlerInvoker;
-import org.junit.Before;
-import org.junit.Test;
-import com.typesafe.config.Config;
-import com.typesafe.config.ConfigFactory;
-import org.junit.runner.RunWith;
-import org.mockito.runners.MockitoJUnitRunner;
-
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ConsumerExecutorFactoryTest {
 
-    ConsumerExecutorFactoryImpl factory;
+    private ConsumerExecutorFactoryImpl factory;
 
     @Before
-    public void setUp() throws Exception {
+    public void setUp() {
         factory = new ConsumerExecutorFactoryImpl();
     }
 
@@ -42,7 +36,7 @@ public void testCreateConsumerThreadPoolBoundedQueue() {
         BlockingQueue queue = threadPoolExecutor.getQueue();
         assertNotNull(queue);
         assertTrue(queue instanceof ArrayBlockingQueue);
-        assertEquals(20, ((ArrayBlockingQueue) queue).remainingCapacity());
+        assertEquals(20, queue.remainingCapacity());
     }
 
     @Test
diff --git a/core/src/test/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvokerTest.java b/core/src/test/java/io/github/tcdl/msb/threading/GroupedMessageHandlerInvokerTest.java
similarity index 84%
rename from core/src/test/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvokerTest.java
rename to core/src/test/java/io/github/tcdl/msb/threading/GroupedMessageHandlerInvokerTest.java
index b4e00e6a..11f37bc7 100644
--- a/core/src/test/java/io/github/tcdl/msb/threading/GroupedExecutorBasedMessageHandlerInvokerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/threading/GroupedMessageHandlerInvokerTest.java
@@ -14,23 +14,30 @@
 
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
-public class GroupedExecutorBasedMessageHandlerInvokerTest {
+public class GroupedMessageHandlerInvokerTest {
     private static final int CONFIG_THREADS = 5;
     private static final int CONFIG_QUEUE = -1;
 
-    ExecutorService[] executors;
+    private ExecutorService[] executors;
 
     @Mock
     AcknowledgementHandlerInternal acknowledgeHandler;
@@ -47,13 +54,13 @@ public class GroupedExecutorBasedMessageHandlerInvokerTest {
     @Mock
     MessageGroupStrategy messageGroupStrategy;
 
-    Message message = TestUtils.createMsbRequestMessage("any0","any0");
+    private Message message = TestUtils.createMsbRequestMessage("any0", "any0");
 
-    Message message1 = TestUtils.createMsbRequestMessage("any1","any1");
+    private Message message1 = TestUtils.createMsbRequestMessage("any1", "any1");
 
-    Message message2 = TestUtils.createMsbRequestMessage("any2","any2");
+    private Message message2 = TestUtils.createMsbRequestMessage("any2", "any2");
 
-    GroupedExecutorBasedMessageHandlerInvoker invoker;
+    private GroupedMessageHandlerInvoker invoker;
 
     @Before
     public void setUp() throws Exception {
@@ -71,7 +78,12 @@ public void setUp() throws Exception {
         when(consumerExecutorFactory.createConsumerThreadPool(1, CONFIG_QUEUE))
                 .thenReturn(executors[0], Arrays.copyOfRange(executors, 1, CONFIG_THREADS));
 
-        invoker = new GroupedExecutorBasedMessageHandlerInvoker(CONFIG_THREADS, CONFIG_QUEUE, consumerExecutorFactory, messageGroupStrategy);
+        List invokers = IntStream
+                .range(0, CONFIG_THREADS)
+                .boxed()
+                .map(i -> new ThreadPoolMessageHandlerInvoker(1, CONFIG_QUEUE, consumerExecutorFactory))
+                .collect(Collectors.toList());
+        invoker = new GroupedMessageHandlerInvoker<>(invokers, messageGroupStrategy);
 
         when(messageGroupStrategy.getMessageGroupId(message)).thenReturn(Optional.of(0));
         when(messageGroupStrategy.getMessageGroupId(message1)).thenReturn(Optional.of(1));

From 06efec4868723115792ae8a3d2cbcf47a4bc652d Mon Sep 17 00:00:00 2001
From: bohdan 
Date: Mon, 19 Nov 2018 12:41:55 +0100
Subject: [PATCH 223/226] Add tests

---
 .../ConsumerExecutorFactoryTest.java          | 11 ++++
 .../MessageHandlerInvokerFactoryTest.java     | 61 +++++++++++++++++++
 2 files changed, 72 insertions(+)
 create mode 100644 core/src/test/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactoryTest.java

diff --git a/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java b/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java
index d03e98e3..74c58e6a 100644
--- a/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/threading/ConsumerExecutorFactoryTest.java
@@ -11,8 +11,10 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 
+import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -52,4 +54,13 @@ public void testCreateConsumerThreadPoolUnboundedQueue() {
         assertNotNull(queue);
         assertTrue(queue instanceof LinkedBlockingQueue);
     }
+
+    @Test
+    public void testThreadNames() {
+        ExecutorService executor1 = factory.createConsumerThreadPool(1, -1);
+        ExecutorService executor2 = factory.createConsumerThreadPool(1, -1);
+
+        executor1.execute(() -> assertThat(Thread.currentThread().getName(), is("msb-consumer-thread-1")));
+        executor2.execute(() -> assertThat(Thread.currentThread().getName(), is("msb-consumer-thread-2")));
+    }
 }
diff --git a/core/src/test/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactoryTest.java b/core/src/test/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactoryTest.java
new file mode 100644
index 00000000..7b169218
--- /dev/null
+++ b/core/src/test/java/io/github/tcdl/msb/threading/MessageHandlerInvokerFactoryTest.java
@@ -0,0 +1,61 @@
+package io.github.tcdl.msb.threading;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.concurrent.ExecutorService;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MessageHandlerInvokerFactoryTest {
+    @Mock
+    private ConsumerExecutorFactory consumerExecutorFactory;
+
+    private MessageHandlerInvokerFactory messageHandlerInvokerFactory;
+
+    @Before
+    public void setUp() {
+        messageHandlerInvokerFactory = new MessageHandlerInvokerFactoryImpl(consumerExecutorFactory);
+        when(consumerExecutorFactory.createConsumerThreadPool(anyInt(), anyInt()))
+                .thenReturn(mock(ExecutorService.class));
+    }
+
+    @Test
+    public void testCreateDirectHandlerInvoker() {
+        MessageHandlerInvoker directInvoker = messageHandlerInvokerFactory.createDirectHandlerInvoker();
+
+        assertThat(directInvoker, instanceOf(DirectMessageHandlerInvoker.class));
+        verifyZeroInteractions(consumerExecutorFactory);
+    }
+
+    @Test
+    public void testCreateExecutorBasedHandlerInvoker() {
+        MessageHandlerInvoker executorBasedInvoker = messageHandlerInvokerFactory.createExecutorBasedHandlerInvoker(5, 10);
+
+        assertThat(executorBasedInvoker, instanceOf(ExecutorBasedMessageHandlerInvoker.class));
+        verify(consumerExecutorFactory).createConsumerThreadPool(5, 10);
+    }
+
+    @Test
+    public void testCreateGroupedExecutorBasedHandlerInvoker() {
+        MessageHandlerInvokerFactory messageHandlerInvokerFactorySpy = spy(messageHandlerInvokerFactory);
+        MessageHandlerInvoker executorBasedInvoker = messageHandlerInvokerFactorySpy
+                .createGroupedExecutorBasedHandlerInvoker(2, -1, mock(MessageGroupStrategy.class));
+
+        assertThat(executorBasedInvoker, instanceOf(GroupedMessageHandlerInvoker.class));
+        verify(messageHandlerInvokerFactorySpy, times(2))
+                .createExecutorBasedHandlerInvoker(1, -1);
+    }
+}

From 18d7d91026a0f14725fe1919629b24bc0134534e Mon Sep 17 00:00:00 2001
From: bohdan 
Date: Tue, 20 Nov 2018 16:24:49 +0100
Subject: [PATCH 224/226] Create direct invoker from factory

---
 .../tcdl/msb/api/MsbContextBuilder.java       | 25 +++++++++----------
 .../DirectInvocationCapableInvoker.java       |  6 ++---
 2 files changed, 15 insertions(+), 16 deletions(-)

diff --git a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
index 4f386b58..34a1cd9e 100644
--- a/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
+++ b/core/src/main/java/io/github/tcdl/msb/api/MsbContextBuilder.java
@@ -21,7 +21,6 @@
 import io.github.tcdl.msb.support.JsonValidator;
 import io.github.tcdl.msb.threading.ConsumerExecutorFactoryImpl;
 import io.github.tcdl.msb.threading.DirectInvocationCapableInvoker;
-import io.github.tcdl.msb.threading.DirectMessageHandlerInvoker;
 import io.github.tcdl.msb.threading.MessageGroupStrategy;
 import io.github.tcdl.msb.threading.MessageHandlerInvoker;
 import io.github.tcdl.msb.threading.MessageHandlerInvokerFactory;
@@ -164,21 +163,21 @@ public void run() {
     }
 
     private MessageHandlerInvoker createMessageHandlerInvoker(AdapterFactory adapterFactory, MsbConfig msbConfig) {
+        MessageHandlerInvoker directMessageHandlerInvoker = messageHandlerInvokerFactory.createDirectHandlerInvoker();
+        if (!adapterFactory.isUseMsbThreadingModel()) {
+            return directMessageHandlerInvoker;
+        }
         MessageHandlerInvoker consumerMessageHandlerInvoker;
-        if (adapterFactory.isUseMsbThreadingModel()) {
-            if (messageGroupStrategy == null) {
-                consumerMessageHandlerInvoker = messageHandlerInvokerFactory.createExecutorBasedHandlerInvoker(
-                        msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity());
-            } else {
-                consumerMessageHandlerInvoker = messageHandlerInvokerFactory.createGroupedExecutorBasedHandlerInvoker(
-                        msbConfig.getConsumerThreadPoolSize(),
-                        msbConfig.getConsumerThreadPoolQueueCapacity(),
-                        messageGroupStrategy);
-            }
+        if (messageGroupStrategy == null) {
+            consumerMessageHandlerInvoker = messageHandlerInvokerFactory.createExecutorBasedHandlerInvoker(
+                    msbConfig.getConsumerThreadPoolSize(), msbConfig.getConsumerThreadPoolQueueCapacity());
         } else {
-            consumerMessageHandlerInvoker = messageHandlerInvokerFactory.createDirectHandlerInvoker();
+            consumerMessageHandlerInvoker = messageHandlerInvokerFactory.createGroupedExecutorBasedHandlerInvoker(
+                    msbConfig.getConsumerThreadPoolSize(),
+                    msbConfig.getConsumerThreadPoolQueueCapacity(),
+                    messageGroupStrategy);
         }
-        return new DirectInvocationCapableInvoker(consumerMessageHandlerInvoker, new DirectMessageHandlerInvoker());
+        return new DirectInvocationCapableInvoker(consumerMessageHandlerInvoker, directMessageHandlerInvoker);
     }
 
     /**
diff --git a/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java b/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java
index 4c815347..fdab9733 100644
--- a/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java
+++ b/core/src/main/java/io/github/tcdl/msb/threading/DirectInvocationCapableInvoker.java
@@ -13,13 +13,13 @@
 public class DirectInvocationCapableInvoker implements MessageHandlerInvoker {
 
     private final MessageHandlerInvoker clientMessageHandlerInvoker;
-    private final DirectMessageHandlerInvoker directMessageHandlerInvoker;
+    private final MessageHandlerInvoker directMessageHandlerInvoker;
 
     /**
-     * Creates composite delegate that is guarantied to have an instance of {@link DirectMessageHandlerInvoker} in its disposal.
+     * Creates composite delegate that is guarantied to have an instance of direct {@link MessageHandlerInvoker} in its disposal.
      * There is no need to instantiate it in client code. It is intended to be used only internally by the library.
      */
-    public DirectInvocationCapableInvoker(MessageHandlerInvoker clientMessageHandlerInvoker, DirectMessageHandlerInvoker directMessageHandlerInvoker) {
+    public DirectInvocationCapableInvoker(MessageHandlerInvoker clientMessageHandlerInvoker, MessageHandlerInvoker directMessageHandlerInvoker) {
         Validate.notNull(clientMessageHandlerInvoker);
         Validate.notNull(directMessageHandlerInvoker);
         this.clientMessageHandlerInvoker = clientMessageHandlerInvoker;

From e2296aa62196232db931a9b6b9d7924611d8c84c Mon Sep 17 00:00:00 2001
From: Roman Drozdov 
Date: Wed, 24 Apr 2019 12:56:44 +0300
Subject: [PATCH 225/226] IN-8118: activemq adapter

* IN-8118: added adapter for active mq

* IN-8118: support routing keys with virtual destinations

* IN-8118: added integration tests

* IN-8118: refactored session management

* IN-8118: refactored session management

* IN-8118: queue/topic autoremove for tests

* IN-8118: added ack test and improved msb clean up
---
 .travis.yml                                   |   6 +
 activemq/pom.xml                              |  49 ++++++
 .../ActiveMQAcknowledgementAdapter.java       |  46 +++++
 .../activemq/ActiveMQAdapterFactory.java      | 163 ++++++++++++++++++
 .../activemq/ActiveMQConnectionManager.java   |  81 +++++++++
 .../activemq/ActiveMQConsumerAdapter.java     |  97 +++++++++++
 .../activemq/ActiveMQMessageConsumer.java     |  62 +++++++
 .../activemq/ActiveMQProducerAdapter.java     |  99 +++++++++++
 .../activemq/ActiveMQSessionManager.java      | 138 +++++++++++++++
 .../tcdl/msb/api/ActiveMQRequestOptions.java  |  49 ++++++
 .../msb/api/ActiveMQResponderOptions.java     |  62 +++++++
 .../github/tcdl/msb/api/SubscriptionType.java |   6 +
 .../config/activemq/ActiveMQBrokerConfig.java |  98 +++++++++++
 activemq/src/main/resources/activemq.conf     |  17 ++
 .../tcdl/msb/ActiveMQAcknowledgeTest.java     |  75 ++++++++
 .../msb/ActiveMQMultipleConsumersTest.java    |  84 +++++++++
 .../msb/ActiveMQRequesterResponderTest.java   |  63 +++++++
 .../tcdl/msb/ActiveMQRoutingKeyTest.java      | 118 +++++++++++++
 activemq/src/test/resources/application.conf  |  14 ++
 activemq/src/test/resources/logback-test.xml  |  14 ++
 .../msb/adapters/amqp/AmqpAdapterFactory.java |  10 +-
 .../tcdl/msb/cli/CliMessageSubscriber.java    |   2 +-
 .../msb/cli/CliMessageSubscriberTest.java     |  10 +-
 .../io/github/tcdl/msb/ChannelManager.java    |  11 +-
 .../tcdl/msb/adapters/AdapterFactory.java     |  19 +-
 .../github/tcdl/msb/impl/RequesterImpl.java   |   3 +-
 .../github/tcdl/msb/impl/ResponderImpl.java   |   4 +-
 .../msb/ChannelManagerConcurrentTest.java     |   6 +-
 .../github/tcdl/msb/ChannelManagerTest.java   |   6 +-
 .../tcdl/msb/impl/RequesterImplTest.java      |  12 +-
 .../tcdl/msb/impl/ResponderImplTest.java      |  25 +--
 .../msb/impl/ResponderServerImplTest.java     |   9 +-
 .../adapterfactory/TestMsbAdapterFactory.java |   4 +-
 pom.xml                                       |  11 ++
 34 files changed, 1404 insertions(+), 69 deletions(-)
 create mode 100644 activemq/pom.xml
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQAcknowledgementAdapter.java
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQAdapterFactory.java
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQConnectionManager.java
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQConsumerAdapter.java
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQMessageConsumer.java
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQProducerAdapter.java
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQSessionManager.java
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/api/ActiveMQRequestOptions.java
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/api/ActiveMQResponderOptions.java
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/api/SubscriptionType.java
 create mode 100644 activemq/src/main/java/io/github/tcdl/msb/config/activemq/ActiveMQBrokerConfig.java
 create mode 100644 activemq/src/main/resources/activemq.conf
 create mode 100644 activemq/src/test/java/io/github/tcdl/msb/ActiveMQAcknowledgeTest.java
 create mode 100644 activemq/src/test/java/io/github/tcdl/msb/ActiveMQMultipleConsumersTest.java
 create mode 100644 activemq/src/test/java/io/github/tcdl/msb/ActiveMQRequesterResponderTest.java
 create mode 100644 activemq/src/test/java/io/github/tcdl/msb/ActiveMQRoutingKeyTest.java
 create mode 100644 activemq/src/test/resources/application.conf
 create mode 100644 activemq/src/test/resources/logback-test.xml

diff --git a/.travis.yml b/.travis.yml
index b3a5f39a..0d51f23c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,12 @@ jdk:
 - oraclejdk8
 services:
 - rabbitmq
+- docker
+before_install:
+  - docker pull webcenter/activemq:latest
+  - docker run -d --name='activemq' -p 8161:8161 -p 61616:61616 -p 61613:61613 webcenter/activemq:latest
+  - docker ps -a
+  - docker inspect activemq
 install: ''
 script:
 - mvn clean deploy --settings settings.xml -B -V
diff --git a/activemq/pom.xml b/activemq/pom.xml
new file mode 100644
index 00000000..1a64f9bd
--- /dev/null
+++ b/activemq/pom.xml
@@ -0,0 +1,49 @@
+
+
+    
+        io.github.tcdl.msb
+        msb-java
+        1.6.7-SNAPSHOT
+        ../pom.xml
+    
+    4.0.0
+    msb-java-activemq
+    msb java activemq
+    msb java activemq
+    jar
+    
+        scm:git:https://github.com/tcdl/msb-java.git
+        scm:git:git@github.com:tcdl/msb-java.git
+        https://github.com/tcdl/msb-java
+        HEAD
+    
+    
+        tcdl
+        https://github.com/tcdl
+    
+    
+        
+            io.github.tcdl.msb
+            msb-java-core
+        
+        
+            io.github.tcdl.msb
+            msb-java-core
+            test-jar
+            test
+        
+        
+            org.apache.activemq
+            activemq-client
+        
+        
+            org.apache.activemq
+            activemq-pool
+        
+        
+            commons-collections
+            commons-collections
+            test
+        
+    
+
diff --git a/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQAcknowledgementAdapter.java b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQAcknowledgementAdapter.java
new file mode 100644
index 00000000..3a155975
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQAcknowledgementAdapter.java
@@ -0,0 +1,46 @@
+package io.github.tcdl.msb.adapters.activemq;
+
+import io.github.tcdl.msb.acknowledge.AcknowledgementAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Message;
+
+/**
+ * ActiveMQ acknowledgement implementation.
+ */
+public class ActiveMQAcknowledgementAdapter implements AcknowledgementAdapter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQAcknowledgementAdapter.class);
+
+    private Message message;
+
+    public ActiveMQAcknowledgementAdapter(Message message) {
+        this.message = message;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void confirm() throws Exception {
+        message.acknowledge();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void reject() throws Exception {
+        message.acknowledge();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void retry() throws Exception {
+        //Do nothing. Unconfirmed message will not be removed from the queue
+    }
+}
+
diff --git a/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQAdapterFactory.java b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQAdapterFactory.java
new file mode 100644
index 00000000..6aecabf0
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQAdapterFactory.java
@@ -0,0 +1,163 @@
+package io.github.tcdl.msb.adapters.activemq;
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import io.github.tcdl.msb.adapters.AdapterFactory;
+import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.api.*;
+import io.github.tcdl.msb.api.exception.AdapterCreationException;
+import io.github.tcdl.msb.api.exception.ChannelException;
+import io.github.tcdl.msb.api.exception.ConfigurationException;
+import io.github.tcdl.msb.config.MsbConfig;
+import io.github.tcdl.msb.config.activemq.ActiveMQBrokerConfig;
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.ActiveMQPrefetchPolicy;
+import org.apache.activemq.RedeliveryPolicy;
+import org.apache.activemq.jms.pool.PooledConnectionFactory;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Optional;
+
+/**
+ * ActiveMQAdapterFactory is an implementation of {@link AdapterFactory}
+ * for {@link ActiveMQAdapterFactory} and {@link ActiveMQConsumerAdapter}
+ */
+public class ActiveMQAdapterFactory implements AdapterFactory {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQAdapterFactory.class);
+
+    private volatile ActiveMQBrokerConfig brokerConfig;
+    private volatile ActiveMQConnectionManager connectionManager;
+
+    /**
+     * @throws ChannelException if an error is encountered during connecting to broker
+     * @throws ConfigurationException if provided configuration is broken
+     */
+    @Override
+    public void init(MsbConfig msbConfig) {
+        brokerConfig = createActiveMQBrokerConfig(msbConfig);
+        LOG.debug("MSB ActiveMQ Broker configuration {}", brokerConfig);
+        PooledConnectionFactory connectionFactory = createConnectionFactory(brokerConfig);
+        connectionManager = createConnectionManager(connectionFactory);
+    }
+
+    private ActiveMQBrokerConfig createActiveMQBrokerConfig(MsbConfig msbConfig) {
+        Config applicationConfig = msbConfig.getBrokerConfig();
+        Config brokerConfig = ConfigFactory.load("activemq").getConfig("config.activemq");
+
+        Config commonConfig = ConfigFactory.defaultOverrides()
+                .withFallback(applicationConfig)
+                .withFallback(brokerConfig);
+
+        return new ActiveMQBrokerConfig.ActiveMQBrokerConfigBuilder().withConfig(commonConfig).build();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ActiveMQProducerAdapter createProducerAdapter(String topic, boolean isResponseTopic, RequestOptions requestOptions) {
+        Validate.notEmpty(topic, "topic is mandatory");
+        Validate.notNull(requestOptions, "subscription type is mandatory");
+
+        Class requestOptionsClass = requestOptions.getClass();
+        SubscriptionType subscriptionType;
+
+        if (ActiveMQRequestOptions.class.isAssignableFrom(requestOptionsClass)) {
+            subscriptionType = ((ActiveMQRequestOptions) requestOptions).getSubscriptionType();
+        } else if (requestOptionsClass.equals(RequestOptions.class)) {
+            subscriptionType = isResponseTopic ? SubscriptionType.TOPIC : brokerConfig.getDefaultSubscriptionType();
+        } else {
+            throw new AdapterCreationException("Illegal for this AdapterFactory RequestOptions subclass");
+        }
+
+        return new ActiveMQProducerAdapter(topic, subscriptionType, brokerConfig, connectionManager, isResponseTopic);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ConsumerAdapter createConsumerAdapter(String topic,  boolean isResponseTopic) {
+        return new ActiveMQConsumerAdapter(topic, brokerConfig.getDefaultSubscriptionType(),
+                ResponderOptions.DEFAULTS.getBindingKeys(),
+                brokerConfig, connectionManager, isResponseTopic);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ActiveMQConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic, ResponderOptions responderOptions) {
+        Validate.notEmpty(topic, "topic is mandatory");
+        Validate.notNull(responderOptions, "responderOptions are mandatory");
+
+        Class responderOptionsClass = responderOptions.getClass();
+        SubscriptionType subscriptionType;
+
+        if (ActiveMQResponderOptions.class.isAssignableFrom(responderOptionsClass)) {
+            subscriptionType = ((ActiveMQResponderOptions) responderOptions).getSubscriptionType();
+        } else if (responderOptionsClass.equals(ResponderOptions.class)) {
+            subscriptionType = isResponseTopic ? SubscriptionType.QUEUE : brokerConfig.getDefaultSubscriptionType();
+        } else {
+            throw new AdapterCreationException("Illegal for this AdapterFactory ResponderOptions subclass");
+        }
+
+        return new ActiveMQConsumerAdapter(topic, subscriptionType, responderOptions.getBindingKeys(),
+                brokerConfig, connectionManager, isResponseTopic);
+    }
+
+    private PooledConnectionFactory createConnectionFactory(ActiveMQBrokerConfig brokerConfig)  {
+        String url = brokerConfig.getUrl();
+        Optional username = brokerConfig.getUsername();
+        Optional password = brokerConfig.getPassword();
+
+        ActiveMQPrefetchPolicy activeMQPrefetchPolicy = new ActiveMQPrefetchPolicy();
+        activeMQPrefetchPolicy.setTopicPrefetch(brokerConfig.getPrefetchCount());
+        activeMQPrefetchPolicy.setDurableTopicPrefetch(brokerConfig.getPrefetchCount());
+        activeMQPrefetchPolicy.setQueuePrefetch(brokerConfig.getPrefetchCount());
+
+        RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
+        redeliveryPolicy.setMaximumRedeliveries(1);
+
+        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
+        username.ifPresent(connectionFactory::setUserName);
+        password.ifPresent(connectionFactory::setPassword);
+        connectionFactory.setPrefetchPolicy(activeMQPrefetchPolicy);
+        connectionFactory.setRedeliveryPolicy(redeliveryPolicy);
+        connectionFactory.setMaxThreadPoolSize(brokerConfig.getPrefetchCount());
+
+        PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory();
+        pooledConnectionFactory.setConnectionFactory(connectionFactory);
+        pooledConnectionFactory.setCreateConnectionOnStartup(true);
+        pooledConnectionFactory.setReconnectOnException(true);
+        pooledConnectionFactory.setIdleTimeout(brokerConfig.getConnectionIdleTimeout());
+        pooledConnectionFactory.initConnectionsPool();
+        pooledConnectionFactory.start();
+
+        return pooledConnectionFactory;
+    }
+
+    private ActiveMQConnectionManager createConnectionManager(PooledConnectionFactory connectionFactory) {
+        return new ActiveMQConnectionManager(connectionFactory);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isUseMsbThreadingModel() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void shutdown() {
+        connectionManager.close();
+    }
+}
+
diff --git a/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQConnectionManager.java b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQConnectionManager.java
new file mode 100644
index 00000000..d75a19c5
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQConnectionManager.java
@@ -0,0 +1,81 @@
+package io.github.tcdl.msb.adapters.activemq;
+
+import io.github.tcdl.msb.api.exception.ChannelException;
+import org.apache.activemq.jms.pool.PooledConnectionFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+public class ActiveMQConnectionManager {
+
+    private static Logger LOG = LoggerFactory.getLogger(ActiveMQConnectionManager.class);
+
+    private PooledConnectionFactory connectionFactory;
+    private Map connectionsByClientId;
+    private Map> connectionCloseListeners;
+
+    public ActiveMQConnectionManager(PooledConnectionFactory connectionFactory) {
+        this.connectionFactory = connectionFactory;
+        this.connectionsByClientId = new ConcurrentHashMap<>();
+        this.connectionCloseListeners = new ConcurrentHashMap<>();
+    }
+
+    /**
+     * @throws ChannelException if some problems during connecting to Broker were occurred
+     */
+    public Connection obtainConnection(String clientId) {
+        try {
+            if (!connectionsByClientId.containsKey(clientId)) {
+                Connection connection = openConnection();
+                if (connection.getClientID() == null) {
+                    connection.setClientID(clientId != null ? clientId : UUID.randomUUID().toString());
+                }
+                connection.start();
+                connectionsByClientId.put(clientId, connection);
+            }
+
+            LOG.info("ActiveMQ connection obtained.");
+            return connectionsByClientId.get(clientId);
+        } catch (JMSException e) {
+            throw new ChannelException("Could not obtain ActiveMQ connection", e);
+        }
+    }
+
+    public void addConnectionCloseListener(String clientId, Consumer connectionCloseListener) {
+        connectionCloseListeners.put(clientId, connectionCloseListener);
+    }
+
+
+    public void close() {
+        connectionsByClientId.forEach((clientId, connection) -> {
+            try {
+                if (connectionCloseListeners.containsKey(clientId)) {
+                    connectionCloseListeners.get(clientId).accept(connection);
+                }
+                connection.stop();
+                connection.close();
+            } catch (JMSException e) {
+                LOG.error("Error closing connection with exception", e);
+            }
+        });
+
+        connectionFactory.clear();
+        connectionFactory.stop();
+    }
+
+    private Connection openConnection() throws JMSException {
+        Connection connection = connectionFactory.createConnection();
+        if (connection == null) {
+            connectionFactory.initConnectionsPool();
+            connectionFactory.start();
+            connection = connectionFactory.createConnection();
+        }
+        return connection;
+    }
+}
diff --git a/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQConsumerAdapter.java b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQConsumerAdapter.java
new file mode 100644
index 00000000..efcc0b20
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQConsumerAdapter.java
@@ -0,0 +1,97 @@
+package io.github.tcdl.msb.adapters.activemq;
+
+import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import io.github.tcdl.msb.api.SubscriptionType;
+import io.github.tcdl.msb.api.exception.ChannelException;
+import io.github.tcdl.msb.config.activemq.ActiveMQBrokerConfig;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import java.util.Optional;
+import java.util.Set;
+
+public class ActiveMQConsumerAdapter implements ConsumerAdapter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQConsumerAdapter.class);
+
+    private static final String VIRTUAL_DESTINATION_PREFIX = "VirtualTopic.";
+    private static final String CONSUMER_TOPIC_PATTERN = "Consumer.%s.%s";
+
+    private final String physicalTopic;
+    private final String groupId;
+    private final SubscriptionType subscriptionType;
+    private final Set bindingKeys;
+    private final ActiveMQBrokerConfig brokerConfig;
+    private final boolean isResponseTopic;
+
+    private final ActiveMQSessionManager sessionManager;
+    private MessageConsumer consumer;
+
+
+    public ActiveMQConsumerAdapter(String topic, SubscriptionType subscriptionType, Set bindingKeys, ActiveMQBrokerConfig brokerConfig,
+                                   ActiveMQConnectionManager connectionManager, boolean isResponseTopic) {
+        Validate.notNull(topic, "Topic name is required");
+        Validate.notNull(subscriptionType, "Subscription type is required");
+
+        this.groupId = brokerConfig.getGroupId().orElse("msb");
+        this.isResponseTopic = isResponseTopic;
+        this.brokerConfig = brokerConfig;
+        this.subscriptionType = subscriptionType;
+        this.bindingKeys = bindingKeys;
+        this.physicalTopic = formatTopic(topic, groupId);
+        this.sessionManager = ActiveMQSessionManager.instance(connectionManager);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void subscribe(RawMessageHandler msgHandler) {
+        try {
+            ActiveMQMessageConsumer messageConsumer = new ActiveMQMessageConsumer(msgHandler);
+            consumer = sessionManager.createConsumer(physicalTopic, subscriptionType, physicalTopic, bindingKeys, isDurable());
+            consumer.setMessageListener(messageConsumer::handlerMessage);
+        } catch (JMSException e) {
+            throw new ChannelException(String.format("Failed to subscribe to topic %s with binding keys %s", physicalTopic, bindingKeys), e);
+        }
+    }
+
+    private boolean isDurable() {
+        return !isResponseTopic && brokerConfig.isDurable();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void unsubscribe() {
+        try {
+            consumer.close();
+        } catch (JMSException e) {
+            throw new ChannelException(String.format("Failed to unsubscribe from topic %s", physicalTopic), e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Optional messageCount() {
+        throw new UnsupportedOperationException("Message count metric is not supported");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Optional isConnected() {
+        throw new UnsupportedOperationException("IsConnected is not supported");
+    }
+
+    private String formatTopic(String topic, String groupId) {
+        return String.format(CONSUMER_TOPIC_PATTERN, groupId, VIRTUAL_DESTINATION_PREFIX + topic);
+    }
+}
diff --git a/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQMessageConsumer.java b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQMessageConsumer.java
new file mode 100644
index 00000000..eb0e4cce
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQMessageConsumer.java
@@ -0,0 +1,62 @@
+package io.github.tcdl.msb.adapters.activemq;
+
+import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerImpl;
+import io.github.tcdl.msb.acknowledge.AcknowledgementHandlerInternal;
+import io.github.tcdl.msb.adapters.ConsumerAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.TextMessage;
+
+public class ActiveMQMessageConsumer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQMessageConsumer.class);
+
+    private ConsumerAdapter.RawMessageHandler msgHandler;
+
+    public ActiveMQMessageConsumer(ConsumerAdapter.RawMessageHandler msgHandler) {
+        this.msgHandler = msgHandler;
+    }
+
+    public void handlerMessage(Message message) {
+        String messageId = null;
+        try {
+            messageId = message.getJMSMessageID();
+        } catch (JMSException e) {
+            LOG.error("Got exception while extracting message id", e);
+        }
+
+        AcknowledgementHandlerInternal ackHandler = createAcknowledgementHandler(messageId, message);
+
+        if (!(message instanceof TextMessage)) {
+            LOG.error("Unsupported message type {}", message.getClass());
+            ackHandler.confirmMessage();
+        }
+
+        try {
+            String messageBody = ((TextMessage) message).getText();
+            LOG.debug("[consumer tag: {}] Message consumed from broker.", messageId);
+            LOG.trace("Message: {}", messageBody);
+
+            try {
+                msgHandler.onMessage(messageBody, ackHandler);
+                LOG.debug("[consumer tag: {}] Raw message has been handled.", messageId);
+                LOG.trace("Message: {}", messageBody);
+            } catch (Exception e) {
+                LOG.error("[consumer tag: {}] Can't handle a raw message.", messageId, e);
+                LOG.trace("Message: {}", messageBody);
+                throw e;
+            }
+        } catch (Exception e) {
+            LOG.error("[consumer tag: {}] Got exception while processing incoming message. About to send ActiveMQ reject...", messageId, e);
+            ackHandler.autoReject();
+        }
+    }
+
+    private AcknowledgementHandlerInternal createAcknowledgementHandler(String messageId, Message message) {
+        ActiveMQAcknowledgementAdapter adapter = new ActiveMQAcknowledgementAdapter(message);
+        return new AcknowledgementHandlerImpl(adapter, false, messageId);
+    }
+}
\ No newline at end of file
diff --git a/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQProducerAdapter.java b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQProducerAdapter.java
new file mode 100644
index 00000000..f097948c
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQProducerAdapter.java
@@ -0,0 +1,99 @@
+package io.github.tcdl.msb.adapters.activemq;
+
+import io.github.tcdl.msb.adapters.ProducerAdapter;
+import io.github.tcdl.msb.api.SubscriptionType;
+import io.github.tcdl.msb.api.exception.ChannelException;
+import io.github.tcdl.msb.config.activemq.ActiveMQBrokerConfig;
+import joptsimple.internal.Strings;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Destination;
+import javax.jms.Message;
+import javax.jms.MessageProducer;
+
+public class ActiveMQProducerAdapter implements ProducerAdapter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQProducerAdapter.class);
+
+    private static final String VIRTUAL_DESTINATION_PREFIX = "VirtualTopic.";
+    private static final String PRODUCER_ID_PATTERN = "Producer.%s";
+    private static final String ERROR_MESSAGE_TEMPLATE = "Failed to publish message to topic '%s' with routing key '%s'";
+
+    private final String physicalTopic;
+    private final SubscriptionType subscriptionType;
+    private final boolean isResponseTopic;
+    private final ActiveMQBrokerConfig brokerConfig;
+    private final ActiveMQSessionManager sessionManager;
+    private final MessageProducer producer;
+
+    ActiveMQProducerAdapter(String topic, SubscriptionType subscriptionType, ActiveMQBrokerConfig brokerConfig,
+                            ActiveMQConnectionManager connectionManager, boolean isResponseTopic) {
+        Validate.notNull(topic, "Topic is mandatory");
+        Validate.notNull(subscriptionType, "Subscription type is mandatory");
+        Validate.notNull(brokerConfig, "Broker config is mandatory");
+        Validate.notNull(connectionManager, "Connection manager is mandatory");
+
+        this.physicalTopic = formatTopic(topic, subscriptionType, brokerConfig.isDurable());
+        this.subscriptionType = subscriptionType;
+        this.isResponseTopic = isResponseTopic;
+        this.brokerConfig = brokerConfig;
+
+        try {
+            String clientId = String.format(PRODUCER_ID_PATTERN, physicalTopic);
+            this.sessionManager = ActiveMQSessionManager.instance(connectionManager);
+            this.producer = sessionManager.createProducer(physicalTopic, subscriptionType, clientId);
+        } catch (Exception e) {
+            throw new ChannelException("Failed to setup channel from ActiveMQ connection", e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void publish(String jsonMessage) {
+        publish(jsonMessage, Strings.EMPTY);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void publish(String jsonMessage, String routingKey) {
+        try {
+            String clientId = String.format(PRODUCER_ID_PATTERN, physicalTopic);
+            Message message = sessionManager.createMessage(jsonMessage, clientId);
+            LOG.debug("Publishing message. Topic name = [{}], routing key = [{}]", physicalTopic, routingKey);
+
+            //create virtual destination with routing key
+            String destinationTopic = this.physicalTopic;
+            if (StringUtils.isNotBlank(routingKey)) {
+                destinationTopic += "." + routingKey;
+            }
+            destinationTopic += !isDurable() ? ".t" : "";
+
+            Destination destination = sessionManager.createDestination(destinationTopic, subscriptionType, clientId);
+
+            if (!isDurable()) {
+                sessionManager.autoRemove(clientId, destination);
+            }
+
+            producer.send(destination, message);
+        } catch (Exception e) {
+            LOG.error(ERROR_MESSAGE_TEMPLATE, physicalTopic, routingKey);
+            LOG.trace("Message: {}", jsonMessage);
+            throw new ChannelException(String.format(ERROR_MESSAGE_TEMPLATE, physicalTopic, routingKey), e);
+        }
+    }
+
+    private boolean isDurable() {
+        return !isResponseTopic && brokerConfig.isDurable();
+    }
+
+    private String formatTopic(String topic, SubscriptionType subscriptionType, boolean durable) {
+        return VIRTUAL_DESTINATION_PREFIX + topic;
+    }
+}
diff --git a/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQSessionManager.java b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQSessionManager.java
new file mode 100644
index 00000000..734febbc
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/adapters/activemq/ActiveMQSessionManager.java
@@ -0,0 +1,138 @@
+package io.github.tcdl.msb.adapters.activemq;
+
+import io.github.tcdl.msb.api.SubscriptionType;
+import io.github.tcdl.msb.api.exception.ChannelException;
+import org.apache.activemq.ActiveMQConnection;
+import org.apache.activemq.command.ActiveMQDestination;
+import org.apache.activemq.jms.pool.PooledConnection;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.*;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import static io.github.tcdl.msb.api.SubscriptionType.QUEUE;
+
+public class ActiveMQSessionManager {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQSessionManager.class);
+
+    private static ActiveMQSessionManager instance;
+    private ActiveMQConnectionManager connectionManager;
+    private Map sessionsByClientId;
+
+    private ActiveMQSessionManager(ActiveMQConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.sessionsByClientId = new ConcurrentHashMap<>();
+    }
+
+    static ActiveMQSessionManager instance(ActiveMQConnectionManager connectionManager) {
+        if (instance == null) {
+            instance = new ActiveMQSessionManager(connectionManager);
+        }
+        return instance;
+    }
+
+    public MessageProducer createProducer(String topic, SubscriptionType subscriptionType, String clientId) {
+        Validate.notEmpty(topic, "topic is mandatory");
+        Validate.notNull(subscriptionType, "subscription type is mandatory");
+
+        try {
+            // omit destination to specify it later during sending a message
+            MessageProducer producer = getSession(clientId).createProducer(null);
+            producer.setDeliveryMode(DeliveryMode.PERSISTENT);
+            LOG.debug("Created producer on topic '{}'", topic);
+
+            return producer;
+        } catch (JMSException e) {
+            throw new ChannelException("Producer creation failed with exception", e);
+        }
+    }
+
+    public MessageConsumer createConsumer(String topic, SubscriptionType subscriptionType, String clientId, Set bindingKeys, boolean durable) {
+        Validate.notEmpty(topic, "topic is mandatory");
+        Validate.notNull(subscriptionType, "subscription type is mandatory");
+
+        try {
+            //create virtual destination with routing keys
+            String destinationTopic = topic;
+            if (bindingKeys != null && !bindingKeys.isEmpty()) {
+                destinationTopic = bindingKeys.stream()
+                        .filter(StringUtils::isNotBlank)
+                        .map(key -> topic + "." + key)
+                        .collect(Collectors.joining( ","));
+                destinationTopic = StringUtils.isNotBlank(destinationTopic) ? destinationTopic : topic;
+            }
+            destinationTopic += !durable ? ".t" : "";
+
+            Session session = getSession(clientId);
+            Destination destination = createDestination(destinationTopic, subscriptionType, clientId);
+
+            if (!durable) {
+                autoRemove(clientId, destination);
+            }
+
+            MessageConsumer consumer;
+            if (subscriptionType == QUEUE || !durable) {
+                consumer = session.createConsumer(destination);
+            } else  {
+                consumer = session.createDurableSubscriber((Topic) destination, clientId);
+            }
+
+            LOG.debug("Created consumer on topic '{}'", topic);
+
+            return consumer;
+        } catch (JMSException e) {
+            throw new ChannelException("Consumer creation failed with exception", e);
+        }
+    }
+
+    public Destination createDestination(String destinationTopic, SubscriptionType subscriptionType, String clientId) {
+        Session session = getSession(clientId);
+        try {
+            return subscriptionType == QUEUE?
+                    session.createQueue(destinationTopic):
+                    session.createTopic(destinationTopic);
+        } catch (JMSException e) {
+            throw new ChannelException("Topic creation failed with exception", e);
+        }
+    }
+
+    public Message createMessage(String body, String clientId) {
+        try {
+            Session session = getSession(clientId);
+            return session.createTextMessage(body);
+        } catch (JMSException e) {
+            throw new ChannelException("Message creation failed with exception", e);
+        }
+    }
+
+    public void autoRemove(String clientId, Destination destination) {
+        connectionManager.addConnectionCloseListener(clientId, connection -> {
+            try {
+                ActiveMQDestination activeMQDestination = (ActiveMQDestination) destination;
+                LOG.debug("Invoke connection close hook for {}", activeMQDestination.getPhysicalName());
+                ((ActiveMQConnection)((PooledConnection)connection).getConnection()).destroyDestination(activeMQDestination);
+            } catch (JMSException e) {
+                LOG.error("Error executing connection hook for {}", clientId, e);
+            }
+        });
+    }
+
+    private Session getSession(String clientId) {
+        try {
+            if (!sessionsByClientId.containsKey(clientId)) {
+                Session session = connectionManager.obtainConnection(clientId).createSession(false, Session.CLIENT_ACKNOWLEDGE);
+                sessionsByClientId.put(clientId, session);
+            }
+            return sessionsByClientId.get(clientId);
+        } catch (JMSException e) {
+            throw new ChannelException("Session creation failed with exception", e);
+        }
+    }
+}
diff --git a/activemq/src/main/java/io/github/tcdl/msb/api/ActiveMQRequestOptions.java b/activemq/src/main/java/io/github/tcdl/msb/api/ActiveMQRequestOptions.java
new file mode 100644
index 00000000..e26fc42a
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/api/ActiveMQRequestOptions.java
@@ -0,0 +1,49 @@
+package io.github.tcdl.msb.api;
+
+public class ActiveMQRequestOptions extends RequestOptions {
+
+    private final SubscriptionType subscriptionType;
+
+    private ActiveMQRequestOptions(Integer ackTimeout,
+                               Integer responseTimeout,
+                               Integer waitForResponses,
+                               MessageTemplate messageTemplate,
+                               String forwardNamespace,
+                               String routingKey,
+                               SubscriptionType subscriptionType) {
+
+        super(ackTimeout, responseTimeout, waitForResponses, messageTemplate, forwardNamespace, routingKey);
+        this.subscriptionType = subscriptionType;
+    }
+
+    public SubscriptionType getSubscriptionType() {
+        return subscriptionType;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public RequestOptions.Builder asBuilder() {
+        return ((ActiveMQRequestOptions.Builder) (new Builder().from(this))).withSubscriptionType(this.subscriptionType);
+    }
+
+    public static class Builder extends RequestOptions.Builder {
+
+        private SubscriptionType subscriptionType = SubscriptionType.TOPIC;
+
+        public Builder withSubscriptionType(SubscriptionType subscriptionType){
+            this.subscriptionType = subscriptionType;
+            return this;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public RequestOptions build() {
+            return new ActiveMQRequestOptions(ackTimeout, responseTimeout, waitForResponses, messageTemplate,
+                    forwardNamespace, routingKey, subscriptionType);
+        }
+    }
+}
\ No newline at end of file
diff --git a/activemq/src/main/java/io/github/tcdl/msb/api/ActiveMQResponderOptions.java b/activemq/src/main/java/io/github/tcdl/msb/api/ActiveMQResponderOptions.java
new file mode 100644
index 00000000..ae5da39e
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/api/ActiveMQResponderOptions.java
@@ -0,0 +1,62 @@
+package io.github.tcdl.msb.api;
+
+import org.apache.commons.lang3.Validate;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.Set;
+
+public class ActiveMQResponderOptions extends ResponderOptions {
+
+    private final SubscriptionType subscriptionType;
+    private final Set bindingKeys;
+
+    private ActiveMQResponderOptions(Set bindingKeys,
+                                   MessageTemplate messageTemplate,
+                                   SubscriptionType subscriptionType) {
+        super(bindingKeys, messageTemplate);
+        this.bindingKeys = bindingKeys;
+        this.subscriptionType = subscriptionType;
+    }
+
+    public SubscriptionType getSubscriptionType() {
+        return subscriptionType;
+    }
+
+    public Set getBindingKeys() {
+        return bindingKeys;
+    }
+
+    public static class Builder extends ResponderOptions.Builder {
+
+        private SubscriptionType subscriptionType = SubscriptionType.QUEUE;
+        private Set bindingKeys;
+
+        public Builder withMessageTemplate(MessageTemplate responseMessageTemplate) {
+            this.messageTemplate = responseMessageTemplate;
+            return this;
+        }
+
+        public Builder withBindingKeys(Set bindingKeys) {
+            this.bindingKeys = bindingKeys;
+            return this;
+        }
+
+        public Builder withSubscriptionType(@Nonnull SubscriptionType subscriptionType){
+            Validate.notNull(subscriptionType);
+            this.subscriptionType = subscriptionType;
+            return this;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public ResponderOptions build() {
+            Set bindingKeys = this.bindingKeys == null || this.bindingKeys.isEmpty()
+                    ? Collections.emptySet()
+                    : this.bindingKeys;
+            return new ActiveMQResponderOptions(bindingKeys, messageTemplate, subscriptionType);
+        }
+    }
+}
\ No newline at end of file
diff --git a/activemq/src/main/java/io/github/tcdl/msb/api/SubscriptionType.java b/activemq/src/main/java/io/github/tcdl/msb/api/SubscriptionType.java
new file mode 100644
index 00000000..8d3a7a8a
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/api/SubscriptionType.java
@@ -0,0 +1,6 @@
+package io.github.tcdl.msb.api;
+
+public enum SubscriptionType {
+    TOPIC,
+    QUEUE
+}
diff --git a/activemq/src/main/java/io/github/tcdl/msb/config/activemq/ActiveMQBrokerConfig.java b/activemq/src/main/java/io/github/tcdl/msb/config/activemq/ActiveMQBrokerConfig.java
new file mode 100644
index 00000000..ac2b0c9c
--- /dev/null
+++ b/activemq/src/main/java/io/github/tcdl/msb/config/activemq/ActiveMQBrokerConfig.java
@@ -0,0 +1,98 @@
+package io.github.tcdl.msb.config.activemq;
+
+import com.typesafe.config.Config;
+import io.github.tcdl.msb.api.SubscriptionType;
+import io.github.tcdl.msb.config.ConfigurationUtil;
+
+import java.util.Optional;
+
+public class ActiveMQBrokerConfig {
+
+    private String url;
+    private final Optional username;
+    private final Optional password;
+    private final SubscriptionType defaultSubscriptionType;
+    private final Optional groupId;
+    private final boolean durable;
+    private final int prefetchCount;
+    private final int connectionIdleTimeout;
+
+    private ActiveMQBrokerConfig(String url, Optional username, Optional password,
+                                 SubscriptionType defaultSubscriptionType, Optional groupId,
+                                 boolean durable, int prefetchCount, int connectionIdleTimeout) {
+        this.url = url;
+        this.username = username;
+        this.password = password;
+        this.defaultSubscriptionType = defaultSubscriptionType;
+        this.groupId = groupId;
+        this.durable = durable;
+        this.prefetchCount = prefetchCount;
+        this.connectionIdleTimeout = connectionIdleTimeout;
+    }
+
+    public final static class ActiveMQBrokerConfigBuilder {
+
+        private String url;
+        private Optional username;
+        private Optional password;
+        private SubscriptionType defaultSubscriptionType;
+        private Optional groupId;
+        private boolean durable;
+        private int prefetchCount;
+        private int connectionIdleTimeout;
+
+        public ActiveMQBrokerConfigBuilder withConfig(Config config) {
+            this.url = ConfigurationUtil.getString(config, "url");
+            this.username = ConfigurationUtil.getOptionalString(config, "username");
+            this.password = ConfigurationUtil.getOptionalString(config, "password");
+            this.defaultSubscriptionType = SubscriptionType.valueOf(ConfigurationUtil.getString(config, "defaultSubscriptionType").toUpperCase());
+            this.groupId = ConfigurationUtil.getOptionalString(config, "groupId");
+            this.durable = ConfigurationUtil.getBoolean(config, "durable");
+            this.prefetchCount = ConfigurationUtil.getInt(config, "prefetchCount");
+            this.connectionIdleTimeout = ConfigurationUtil.getInt(config, "connectionIdleTimeout");
+            return this;
+        }
+
+        public ActiveMQBrokerConfig build() {
+            return new ActiveMQBrokerConfig(url, username, password, defaultSubscriptionType, groupId, durable, prefetchCount, connectionIdleTimeout);
+        }
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public Optional getUsername() {
+        return username;
+    }
+
+    public Optional getPassword() {
+        return password;
+    }
+
+    public SubscriptionType getDefaultSubscriptionType() {
+        return defaultSubscriptionType;
+    }
+
+    public Optional getGroupId() {
+        return groupId;
+    }
+
+    public boolean isDurable() {
+        return durable;
+    }
+
+    public int getPrefetchCount() {
+        return prefetchCount;
+    }
+
+    public int getConnectionIdleTimeout() {
+        return connectionIdleTimeout;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ActiveMQBrokerConfig [url=%s, username=%s, password=xxx, defaultSubscriptionType=%s, groupId=%s, durable=%s, prefetchCount=%s, connectionIdleTimeout=%s",
+                url, username, defaultSubscriptionType, groupId, durable, prefetchCount, connectionIdleTimeout);
+    }
+}
diff --git a/activemq/src/main/resources/activemq.conf b/activemq/src/main/resources/activemq.conf
new file mode 100644
index 00000000..0a5830ec
--- /dev/null
+++ b/activemq/src/main/resources/activemq.conf
@@ -0,0 +1,17 @@
+# ActiveMQ Broker Adapter Defaults
+config.activemq = {
+
+  url = "tcp://localhost:61616"
+  url = ${?MSB_BROKER_URL}
+  username = "admin"
+  username = ${?MSB_BROKER_USER_NAME}
+  password = "admin"
+  password = ${?MSB_BROKER_PASSWORD}
+
+  durable = true
+  defaultSubscriptionType = "queue"
+  prefetchCount = 10
+  groupId = "msb-java"
+  connectionIdleTimeout = 300000
+}
+
diff --git a/activemq/src/test/java/io/github/tcdl/msb/ActiveMQAcknowledgeTest.java b/activemq/src/test/java/io/github/tcdl/msb/ActiveMQAcknowledgeTest.java
new file mode 100644
index 00000000..5fd0ad53
--- /dev/null
+++ b/activemq/src/test/java/io/github/tcdl/msb/ActiveMQAcknowledgeTest.java
@@ -0,0 +1,75 @@
+package io.github.tcdl.msb;
+
+import com.typesafe.config.ConfigFactory;
+import io.github.tcdl.msb.api.*;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+public class ActiveMQAcknowledgeTest {
+
+    private String namespace = "activemq:acknowledge:test";
+    private MsbContext msbContext;
+    private ResponderServer responderServer;
+
+    @Before
+    public void setUp() {
+        msbContext = new MsbContextBuilder()
+                .enableShutdownHook(true)
+                .withConfig(ConfigFactory.load())
+                .build();
+    }
+
+    @After
+    public void tearDown() throws InterruptedException {
+        responderServer.stop();
+        msbContext.shutdown();
+        TimeUnit.SECONDS.sleep(5);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void retryMessageTest() throws Exception {
+        String message = "test message";
+
+        RequestOptions requestOptions = new ActiveMQRequestOptions.Builder()
+                .withWaitForResponses(0)
+                .build();
+
+        ResponderOptions responderOptions = new ActiveMQResponderOptions.Builder()
+                .build();
+
+        CountDownLatch receivedMessageLatch = new CountDownLatch(1);
+        responderServer = msbContext.getObjectFactory().createResponderServer(namespace, responderOptions,
+                (request, responderContext) -> {
+                    responderContext.getAcknowledgementHandler().retryMessage();
+                    receivedMessageLatch.countDown();
+                }, String.class)
+                .listen();
+
+        msbContext.getObjectFactory().createRequester(namespace, requestOptions, String.class)
+                .publish(message);
+
+        receivedMessageLatch.await(5, TimeUnit.SECONDS);
+
+        Assert.assertEquals(0, receivedMessageLatch.getCount());
+
+        // do restart
+        responderServer.stop();
+
+        // message should be returned to the queue and processed again
+        ResponderServer.RequestHandler handlerMock = mock(ResponderServer.RequestHandler.class);
+        responderServer = msbContext.getObjectFactory().createResponderServer(namespace, responderOptions,
+                handlerMock, String.class).listen();
+
+        verify(handlerMock, timeout(5000).times(1)).process(eq(message), any(ResponderContext.class));
+    }
+}
diff --git a/activemq/src/test/java/io/github/tcdl/msb/ActiveMQMultipleConsumersTest.java b/activemq/src/test/java/io/github/tcdl/msb/ActiveMQMultipleConsumersTest.java
new file mode 100644
index 00000000..be2246c9
--- /dev/null
+++ b/activemq/src/test/java/io/github/tcdl/msb/ActiveMQMultipleConsumersTest.java
@@ -0,0 +1,84 @@
+package io.github.tcdl.msb;
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigValueFactory;
+import io.github.tcdl.msb.api.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+public class ActiveMQMultipleConsumersTest {
+
+    private String namespace = "activemq:multiple-consumers:test";
+    private List msbContexts;
+    private List responderServers;
+
+    @Before
+    public void setUp() {
+        responderServers = new LinkedList<>();
+        msbContexts = new LinkedList<>();
+        msbContexts.add(new MsbContextBuilder()
+                .enableShutdownHook(true)
+                .withConfig(ConfigFactory.load())
+                .build());
+    }
+
+    @After
+    public void tearDown() throws InterruptedException {
+        responderServers.forEach(ResponderServer::stop);
+        responderServers.clear();
+        msbContexts.forEach(MsbContext::shutdown);
+        msbContexts.clear();
+        TimeUnit.SECONDS.sleep(5);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void multipleConsumersTest()  throws Exception {
+        final int numberOfConsumers = 3;
+        final String message = "test message";
+
+        MsbContext msbContext = msbContexts.get(0);
+
+        RequestOptions requestOptions = new ActiveMQRequestOptions.Builder()
+                .withWaitForResponses(0)
+                .build();
+
+        ResponderOptions responderOptions = new ActiveMQResponderOptions.Builder()
+                .build();
+
+        ResponderServer.RequestHandler handlerMock = mock(ResponderServer.RequestHandler.class);
+        IntStream.range(0, numberOfConsumers).forEach((i) -> {
+            Config config = getConfigWith(ConfigFactory.load(), "msbConfig.brokerConfig.groupId", "consumer" + (i+1));
+
+            MsbContext consumerMsbContext = new MsbContextBuilder()
+                    .enableShutdownHook(true)
+                    .withConfig(config)
+                    .build();
+
+            msbContexts.add(consumerMsbContext);
+
+            responderServers.add(consumerMsbContext.getObjectFactory().createResponderServer(namespace, responderOptions,
+                    handlerMock, String.class).listen());
+        });
+
+        msbContext.getObjectFactory().createRequester(namespace, requestOptions)
+                .publish(message);
+
+        verify(handlerMock, timeout(5000).times(numberOfConsumers)).process(eq(message), any(ResponderContext.class));
+    }
+
+    private Config getConfigWith(Config config, String path, Object value) {
+        return  config.withValue(path, ConfigValueFactory.fromAnyRef(value));
+    }
+}
diff --git a/activemq/src/test/java/io/github/tcdl/msb/ActiveMQRequesterResponderTest.java b/activemq/src/test/java/io/github/tcdl/msb/ActiveMQRequesterResponderTest.java
new file mode 100644
index 00000000..59e32ace
--- /dev/null
+++ b/activemq/src/test/java/io/github/tcdl/msb/ActiveMQRequesterResponderTest.java
@@ -0,0 +1,63 @@
+package io.github.tcdl.msb;
+
+import com.typesafe.config.ConfigFactory;
+import io.github.tcdl.msb.api.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+public class ActiveMQRequesterResponderTest {
+
+    private String namespace = "activemq:req-resp:test";
+    private MsbContext msbContext;
+    private ResponderServer responderServer;
+
+    @Before
+    public void setUp() {
+        msbContext = new MsbContextBuilder()
+                .enableShutdownHook(true)
+                .withConfig(ConfigFactory.load())
+                .build();
+    }
+
+    @After
+    public void tearDown() throws InterruptedException {
+        responderServer.stop();
+        msbContext.shutdown();
+        TimeUnit.SECONDS.sleep(5);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void requestResponseTest() throws Exception {
+        String message = "test message";
+
+        RequestOptions requestOptions = new ActiveMQRequestOptions.Builder()
+                .withWaitForResponses(1)
+                .build();
+
+        ResponderOptions responderOptions = new ActiveMQResponderOptions.Builder()
+                .build();
+
+        BiConsumer responseHandlerMock = mock(BiConsumer.class);
+
+        responderServer = msbContext.getObjectFactory().createResponderServer(namespace, responderOptions,
+                (request, responderContext) -> {
+                    responderContext.getResponder().send(request);
+                }, String.class)
+                .listen();
+
+        msbContext.getObjectFactory().createRequester(namespace, requestOptions, String.class)
+                .onResponse(responseHandlerMock)
+                .publish(message);
+
+        verify(responseHandlerMock, timeout(5000).times(1)).accept(eq(message), any(MessageContext.class));
+    }
+}
diff --git a/activemq/src/test/java/io/github/tcdl/msb/ActiveMQRoutingKeyTest.java b/activemq/src/test/java/io/github/tcdl/msb/ActiveMQRoutingKeyTest.java
new file mode 100644
index 00000000..b274afd2
--- /dev/null
+++ b/activemq/src/test/java/io/github/tcdl/msb/ActiveMQRoutingKeyTest.java
@@ -0,0 +1,118 @@
+
+package io.github.tcdl.msb;
+
+import com.google.common.collect.Sets;
+import com.typesafe.config.ConfigFactory;
+import io.github.tcdl.msb.api.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+public class ActiveMQRoutingKeyTest {
+
+    private String namespace = "activemq:rounting-keys:test";
+    private List msbContexts;
+    private List responderServers;
+
+    @Before
+    public void setUp() {
+        msbContexts = new LinkedList<>();
+        responderServers = new LinkedList<>();
+    }
+
+    @After
+    public void tearDown() throws InterruptedException {
+        responderServers.forEach(ResponderServer::stop);
+        responderServers.clear();
+        msbContexts.forEach(MsbContext::shutdown);
+        msbContexts.clear();
+        TimeUnit.SECONDS.sleep(5);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void requestResponseWithRoutingKeyTest() throws Exception {
+        final String message = "test message";
+        final String routingKey = "RK";
+
+        RequestOptions requestOptions = new ActiveMQRequestOptions.Builder()
+                .withRoutingKey(routingKey)
+                .withWaitForResponses(0)
+                .build();
+
+        ResponderOptions responderOptions = new ActiveMQResponderOptions.Builder()
+                .withSubscriptionType(SubscriptionType.QUEUE)
+                .withBindingKeys(Sets.newHashSet(routingKey))
+                .build();
+
+        MsbContext msbContext = new MsbContextBuilder()
+                .enableShutdownHook(true)
+                .withConfig(ConfigFactory.load())
+                .build();
+        msbContexts.add(msbContext);
+
+        ResponderServer.RequestHandler handlerMock = mock(ResponderServer.RequestHandler.class);
+
+        responderServers.add(msbContext.getObjectFactory().createResponderServer(namespace, responderOptions,
+                handlerMock, String.class).listen());
+
+        msbContext.getObjectFactory().createRequester(namespace, requestOptions)
+                .publish(message);
+
+        verify(handlerMock, timeout(5000).times(1)).process(eq(message), any(ResponderContext.class));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void requestResponseWithDifferentRoutingKeyTest() throws Exception {
+        final String message = "test message";
+        final String routingKey1 = "RK1";
+        final String routingKey2 = "RK2";
+
+        RequestOptions requestOptions = new ActiveMQRequestOptions.Builder()
+                .withRoutingKey(routingKey1)
+                .withWaitForResponses(0)
+                .build();
+
+        ResponderOptions responderOptions1 = new ActiveMQResponderOptions.Builder()
+                .withBindingKeys(Sets.newHashSet(routingKey1))
+                .build();
+
+        ResponderOptions responderOptions2 = new ActiveMQResponderOptions.Builder()
+                .withBindingKeys(Sets.newHashSet(routingKey2))
+                .build();
+
+        MsbContext msbContext1 = new MsbContextBuilder()
+                .enableShutdownHook(true)
+                .withConfig(ConfigFactory.load())
+                .build();
+        msbContexts.add(msbContext1);
+
+        MsbContext msbContext2 = new MsbContextBuilder()
+                .enableShutdownHook(true)
+                .withConfig(ConfigFactory.load())
+                .build();
+        msbContexts.add(msbContext2);
+
+        ResponderServer.RequestHandler handlerMock = mock(ResponderServer.RequestHandler.class);
+
+        responderServers.add(msbContext1.getObjectFactory().createResponderServer(namespace, responderOptions1,
+                handlerMock, String.class).listen());
+
+        responderServers.add(msbContext2.getObjectFactory().createResponderServer(namespace, responderOptions2,
+                handlerMock, String.class).listen());
+
+        msbContext1.getObjectFactory().createRequester(namespace, requestOptions)
+                .publish(message);
+
+        verify(handlerMock, timeout(5000).times(1)).process(eq(message), any(ResponderContext.class));
+    }
+}
diff --git a/activemq/src/test/resources/application.conf b/activemq/src/test/resources/application.conf
new file mode 100644
index 00000000..b19b1b4e
--- /dev/null
+++ b/activemq/src/test/resources/application.conf
@@ -0,0 +1,14 @@
+msbConfig {
+  serviceDetails = {
+    name = "activemq-integration-test"
+    instanceId = "activemq-integration-test-001"
+    version = "1.0.0"
+  }
+
+  brokerAdapterFactory = "io.github.tcdl.msb.adapters.activemq.ActiveMQAdapterFactory"
+
+  brokerConfig = {
+    groupId = "int-test"
+    durable = false
+  }
+}
\ No newline at end of file
diff --git a/activemq/src/test/resources/logback-test.xml b/activemq/src/test/resources/logback-test.xml
new file mode 100644
index 00000000..17d61d64
--- /dev/null
+++ b/activemq/src/test/resources/logback-test.xml
@@ -0,0 +1,14 @@
+
+
+    
+        
+            UTF-8
+            %d{HH:mm:ss.SSS} %-5level %logger{1} [%t] tags[%X{msbTags}] corrId[%X{msbCorrelationId}] customTagKey[%X{customTagKey}] - %m%n
+        
+    
+
+    
+        
+        
+    
+
\ No newline at end of file
diff --git a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
index ecc846fb..6faf22f9 100644
--- a/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
+++ b/amqp/src/main/java/io/github/tcdl/msb/adapters/amqp/AmqpAdapterFactory.java
@@ -7,11 +7,7 @@
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
 import io.github.tcdl.msb.adapters.AdapterFactory;
-import io.github.tcdl.msb.api.AmqpRequestOptions;
-import io.github.tcdl.msb.api.AmqpResponderOptions;
-import io.github.tcdl.msb.api.ExchangeType;
-import io.github.tcdl.msb.api.RequestOptions;
-import io.github.tcdl.msb.api.ResponderOptions;
+import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.exception.AdapterCreationException;
 import io.github.tcdl.msb.api.exception.ChannelException;
 import io.github.tcdl.msb.api.exception.ConfigurationException;
@@ -66,7 +62,7 @@ protected AmqpBrokerConfig createAmqpBrokerConfig(MsbConfig msbConfig) {
     }
     
     @Override
-    public AmqpProducerAdapter createProducerAdapter(String topic, RequestOptions requestOptions) {
+    public AmqpProducerAdapter createProducerAdapter(String topic, boolean isResponseTopic, RequestOptions requestOptions) {
         Validate.notNull(topic, "topic is mandatory");
         Validate.notNull(requestOptions, "requestOptions are mandatory");
 
@@ -92,7 +88,7 @@ public AmqpConsumerAdapter createConsumerAdapter(String topic, boolean isRespons
     }
 
     @Override
-    public AmqpConsumerAdapter createConsumerAdapter(String topic, ResponderOptions responderOptions, boolean isResponseTopic) {
+    public AmqpConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic, ResponderOptions responderOptions) {
         Validate.notEmpty(topic, "topic is mandatory");
         Validate.notNull(responderOptions, "responderOptions are mandatory");
 
diff --git a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java
index f023ebb3..b6ddf994 100644
--- a/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java
+++ b/cli/src/main/java/io/github/tcdl/msb/cli/CliMessageSubscriber.java
@@ -31,7 +31,7 @@ public void subscribe(String topicName, ExchangeType exchangeType, CliMessageHan
                         .withExchangeType(exchangeType)
                         .withBindingKeys(Sets.newHashSet("*"))
                         .build();
-                ConsumerAdapter adapter = adapterFactory.createConsumerAdapter(topicName, responderOptions, false);
+                ConsumerAdapter adapter = adapterFactory.createConsumerAdapter(topicName, false, responderOptions);
 
                 adapter.subscribe(handler);
                 registeredTopics.add(topicName);
diff --git a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java
index 14461501..1961ef10 100644
--- a/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java
+++ b/cli/src/test/java/io/github/tcdl/msb/cli/CliMessageSubscriberTest.java
@@ -9,11 +9,7 @@
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 public class CliMessageSubscriberTest {
 
@@ -29,7 +25,7 @@ public class CliMessageSubscriberTest {
     public void setUp() {
         mockAdapterFactory = mock(AdapterFactory.class);
         mockConsumerAdapter = mock(ConsumerAdapter.class);
-        when(mockAdapterFactory.createConsumerAdapter(eq(TOPIC_NAME), any(ResponderOptions.class),eq(false))).thenReturn(mockConsumerAdapter);
+        when(mockAdapterFactory.createConsumerAdapter(eq(TOPIC_NAME), eq(false), any(ResponderOptions.class))).thenReturn(mockConsumerAdapter);
 
         mockMessageHandler = mock(CliMessageHandler.class);
 
@@ -57,7 +53,7 @@ private void testInitialSubscription(String topicName) {
         // method under test
         subscriptionManager.subscribe(topicName, ExchangeType.FANOUT, mockMessageHandler);
 
-        verify(mockAdapterFactory).createConsumerAdapter(eq(topicName), any(ResponderOptions.class),eq(false));
+        verify(mockAdapterFactory).createConsumerAdapter(eq(topicName), eq(false), any(ResponderOptions.class));
         verify(mockConsumerAdapter).subscribe(mockMessageHandler);
     }
 }
\ No newline at end of file
diff --git a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
index 3940755e..b8160eea 100644
--- a/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
+++ b/core/src/main/java/io/github/tcdl/msb/ChannelManager.java
@@ -52,12 +52,12 @@ public ChannelManager(MsbConfig msbConfig, Clock clock, JsonValidator validator,
         this.consumersByTopic = new ConcurrentHashMap<>();
     }
 
-    public Producer findOrCreateProducer(String topic, RequestOptions requestOptions) {
+    public Producer findOrCreateProducer(String topic, boolean isResponseTopic, RequestOptions requestOptions) {
         Validate.notEmpty(topic, "Topic is mandatory");
         Validate.notNull(requestOptions, "RequestOptions are mandatory");
 
         Producer producer = producersByTopic.computeIfAbsent(topic, key -> {
-            Producer newProducer = createProducer(key, requestOptions);
+            Producer newProducer = createProducer(key, isResponseTopic, requestOptions);
             return newProducer;
         });
 
@@ -134,20 +134,21 @@ private void stopConsumer(Consumer consumer) {
         }
     }
 
-    private Producer createProducer(String topic, RequestOptions requestOptions) {
+    private Producer createProducer(String topic, boolean isResponseTopic, RequestOptions requestOptions) {
         Utils.validateTopic(topic);
-        ProducerAdapter adapter = this.adapterFactory.createProducerAdapter(topic, requestOptions);
+        ProducerAdapter adapter = this.adapterFactory.createProducerAdapter(topic, isResponseTopic, requestOptions);
         return new Producer(adapter, topic, messageMapper);
     }
 
     private Consumer createConsumer(String topic, boolean isResponseTopic, ResponderOptions responderOptions, MessageHandlerResolver messageHandlerResolver) {
         Utils.validateTopic(topic);
-        ConsumerAdapter adapter = this.adapterFactory.createConsumerAdapter(topic, responderOptions, isResponseTopic);
+        ConsumerAdapter adapter = this.adapterFactory.createConsumerAdapter(topic, isResponseTopic, responderOptions);
         return new Consumer(adapter, messageHandlerInvoker, topic, messageHandlerResolver, msbConfig, clock, validator, messageMapper);
     }
 
     public void shutdown() {
         LOG.info("Shutting down...");
+        consumersByTopic.values().forEach(this::stopConsumer);
         messageHandlerInvoker.shutdown();
         adapterFactory.shutdown();
         LOG.info("Shutdown complete");
diff --git a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
index 210ef477..b76de4be 100644
--- a/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
+++ b/core/src/main/java/io/github/tcdl/msb/adapters/AdapterFactory.java
@@ -24,18 +24,25 @@ public interface AdapterFactory {
      * @param topic topic name
      * @return Producer Adapter associated with a topic
      * @throws ChannelException if some problems during creation were occurred
-     * @deprecated use {@link AdapterFactory#createProducerAdapter(String, RequestOptions)}
+     * @deprecated use {@link AdapterFactory#createProducerAdapter(String, boolean, RequestOptions)}
      */
     @Deprecated
     default ProducerAdapter createProducerAdapter(String topic) {
-        return createProducerAdapter(topic, RequestOptions.DEFAULTS);
+        return createProducerAdapter(topic, false, RequestOptions.DEFAULTS);
     }
 
-    ProducerAdapter createProducerAdapter(String topic, RequestOptions requestOptions);
-
     /**
      * @param topic topic name
      * @param isResponseTopic specify if this topic used to handle response
+     * @param requestOptions specific options depending on adapter implementation
+     * @return Producer Adapter associated with a topic
+     * @throws ChannelException if some problems during creation were occurred
+     */
+    ProducerAdapter createProducerAdapter(String topic, boolean isResponseTopic, RequestOptions requestOptions);
+
+    /**
+     * @param topic topic name
+     * @param isResponseTopic specify if topic for responses
      * @return Consumer Adapter associated with a topic
      * @throws ChannelException if some problems during creation were occurred
      */
@@ -44,9 +51,11 @@ default ProducerAdapter createProducerAdapter(String topic) {
     /**
      * Creates ConsumerAdapter associated with a topic.
      * @param topic topic name
+     * @param isResponseTopic specify if this topic used to handle response
+     * @param responderOptions specific options depending on adapter implementation
      * @throws ChannelException if a problems has occurred during creation
      */
-    ConsumerAdapter createConsumerAdapter(String topic, ResponderOptions responderOptions, boolean isResponseTopic);
+    ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic, ResponderOptions responderOptions);
 
     /**
      * @return true if custom MSB threading model should be used.
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
index 7416e463..88520e63 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/RequesterImpl.java
@@ -5,7 +5,6 @@
 import io.github.tcdl.msb.api.*;
 import io.github.tcdl.msb.api.message.Acknowledge;
 import io.github.tcdl.msb.api.message.Message;
-import io.github.tcdl.msb.api.message.Topics;
 import io.github.tcdl.msb.collector.Collector;
 import io.github.tcdl.msb.events.EventHandlers;
 import io.github.tcdl.msb.message.MessageFactory;
@@ -177,7 +176,7 @@ private void publish(boolean invokeHandlersDirectly, Object requestPayload, Mess
     }
 
     private void publishMessage(Message message) {
-        getChannelManager().findOrCreateProducer(message.getTopics().getTo(), requestOptions).publish(message);
+        getChannelManager().findOrCreateProducer(message.getTopics().getTo(), false, requestOptions).publish(message);
     }
 
     private boolean isWaitForAckMs() {
diff --git a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java
index d6fa9794..4f0886ff 100644
--- a/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java
+++ b/core/src/main/java/io/github/tcdl/msb/impl/ResponderImpl.java
@@ -2,7 +2,6 @@
 
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.Producer;
-import io.github.tcdl.msb.api.AcknowledgementHandler;
 import io.github.tcdl.msb.api.MessageTemplate;
 import io.github.tcdl.msb.api.RequestOptions;
 import io.github.tcdl.msb.api.Responder;
@@ -10,7 +9,6 @@
 import io.github.tcdl.msb.api.message.Message;
 import io.github.tcdl.msb.message.MessageFactory;
 import io.github.tcdl.msb.support.Utils;
-
 import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -61,7 +59,7 @@ public void send(Object responsePayload) {
     }
 
     private void sendMessage(Message message) {
-        Producer producer = channelManager.findOrCreateProducer(message.getTopics().getTo(), RequestOptions.DEFAULTS);
+        Producer producer = channelManager.findOrCreateProducer(message.getTopics().getTo(), true, RequestOptions.DEFAULTS);
         LOG.debug("Publishing message to topic : {}", message.getTopics().getTo());
         producer.publish(message);
     }
diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java
index b726eb1a..599c6cf4 100644
--- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerConcurrentTest.java
@@ -41,9 +41,9 @@ public void setUp() {
     public void testProducerCachedMultithreadInteraction() {
         String topic = "topic:test-producer-cached-multithreaded";
 
-        new MultithreadingTester().add(() -> {channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS);}).run();
+        new MultithreadingTester().add(() -> {channelManager.findOrCreateProducer(topic, false, RequestOptions.DEFAULTS);}).run();
 
-        verify(adapterFactory, times(1)).createProducerAdapter(eq(topic), eq(RequestOptions.DEFAULTS));
+        verify(adapterFactory, times(1)).createProducerAdapter(eq(topic), eq(false), eq(RequestOptions.DEFAULTS));
     }
 
     @Test
@@ -53,7 +53,7 @@ public void testConsumerUnsubscribeMultithreadInteraction() {
         CollectorManager collectorManager = new CollectorManager(topic, channelManager);
 
         ConsumerAdapter consumerAdapter = mock(ConsumerAdapter.class);
-        when(adapterFactory.createConsumerAdapter(eq(topic), any(ResponderOptions.class), eq(true))).thenReturn(consumerAdapter);
+        when(adapterFactory.createConsumerAdapter(eq(topic), eq(true), any(ResponderOptions.class))).thenReturn(consumerAdapter);
 
         channelManager.subscribeForResponses(topic, collectorManager);
 
diff --git a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java
index 6386abb5..dcc2dc12 100644
--- a/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/ChannelManagerTest.java
@@ -52,11 +52,11 @@ public void testProducerCached() {
         String topic = "topic:test-producer-cached";
 
         // Producer was created
-        Producer producer1 = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS);
+        Producer producer1 = channelManager.findOrCreateProducer(topic, false, RequestOptions.DEFAULTS);
         assertNotNull(producer1);
 
         // Cached producer was returned
-        Producer producer2 = channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS);
+        Producer producer2 = channelManager.findOrCreateProducer(topic, false, RequestOptions.DEFAULTS);
         assertNotNull(producer2);
         assertSame(producer1, producer2);
     }
@@ -98,7 +98,7 @@ public void testReceiveMessageInvokesHandler() throws InterruptedException {
                     messageEvent.value = msg;
                     awaitReceiveEvents.countDown();
                 });
-        channelManager.findOrCreateProducer(topic, RequestOptions.DEFAULTS).publish(message);
+        channelManager.findOrCreateProducer(topic, false, RequestOptions.DEFAULTS).publish(message);
 
         assertTrue(awaitReceiveEvents.await(4000, TimeUnit.MILLISECONDS));
         assertNotNull(messageEvent.value);
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
index 84ce7e63..35b99141 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/RequesterImplTest.java
@@ -24,11 +24,7 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.*;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.same;
+import static org.mockito.Matchers.*;
 import static org.mockito.Mockito.*;
 
 /**
@@ -329,7 +325,7 @@ public void testRequest_acknowledgeHandlerCancelsFutureOnTooManyResponses() thro
     public void testRequestMessage() throws Exception {
         ChannelManager channelManagerMock = mock(ChannelManager.class);
         Producer producerMock = mock(Producer.class);
-        when(channelManagerMock.findOrCreateProducer(eq(TOPIC), eq(RequestOptions.DEFAULTS))).thenReturn(producerMock);
+        when(channelManagerMock.findOrCreateProducer(eq(TOPIC), eq(false), eq(RequestOptions.DEFAULTS))).thenReturn(producerMock);
         ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
 
         MsbContextImpl msbContext = TestUtils.createMsbContextBuilder()
@@ -366,7 +362,7 @@ public void testRequestMessageWithTags() throws Exception {
         RestPayload requestPayload = TestUtils.createSimpleRequestPayload();
         RequestOptions requestOptions = TestUtils.createSimpleRequestOptionsWithTags(tag);
 
-        when(channelManagerMock.findOrCreateProducer(eq(TOPIC), eq(requestOptions))).thenReturn(producerMock);
+        when(channelManagerMock.findOrCreateProducer(eq(TOPIC), eq(false), eq(requestOptions))).thenReturn(producerMock);
 
         Requester requester = RequesterImpl.create(TOPIC, requestOptions, msbContext, new TypeReference(){});
         requester.publish(requestPayload, dynamicTag1, dynamicTag2, nullTag);
@@ -389,7 +385,7 @@ private RequesterImpl initRequesterForResponsesWith(Integer numberO
                 .withAckTimeout(ackTimeout)
                 .build();
 
-        when(channelManagerMock.findOrCreateProducer(anyString(), any(RequestOptions.class))).thenReturn(producerMock);
+        when(channelManagerMock.findOrCreateProducer(anyString(), eq(false), any(RequestOptions.class))).thenReturn(producerMock);
 
         return setUpRequester(TOPIC, onResponse, onAcknowledge, onError, endHandler, requestOptions);
     }
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java
index a5167c6e..191cb627 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderImplTest.java
@@ -1,17 +1,5 @@
 package io.github.tcdl.msb.impl;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 import io.github.tcdl.msb.ChannelManager;
 import io.github.tcdl.msb.Producer;
 import io.github.tcdl.msb.api.MessageTemplate;
@@ -21,13 +9,16 @@
 import io.github.tcdl.msb.config.MsbConfig;
 import io.github.tcdl.msb.message.MessageFactory;
 import io.github.tcdl.msb.support.TestUtils;
-
-import java.time.Clock;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import java.time.Clock;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
 public class ResponderImplTest {
 
     private MessageTemplate messageTemplate;
@@ -58,7 +49,7 @@ public void setUp() {
 
         when(msbContextSpy.getChannelManager()).thenReturn(mockChannelManager);
         when(msbContextSpy.getMessageFactory()).thenReturn(spyMessageFactory);
-        when(mockChannelManager.findOrCreateProducer(anyString(), any(RequestOptions.class))).thenReturn(mockProducer);
+        when(mockChannelManager.findOrCreateProducer(anyString(), eq(true), any(RequestOptions.class))).thenReturn(mockProducer);
 
         responder = new ResponderImpl(messageTemplate, originalMessage, msbContextSpy);
     }
@@ -75,7 +66,7 @@ public void testProducerWasCreatedForProperTopic() {
         ArgumentCaptor argument = ArgumentCaptor.forClass(String.class);
         responder.send("");
 
-        verify(mockChannelManager).findOrCreateProducer(argument.capture(), any(RequestOptions.class));
+        verify(mockChannelManager).findOrCreateProducer(argument.capture(), anyBoolean(), any(RequestOptions.class));
 
         assertEquals(originalMessage.getTopics().getResponse(), argument.getValue());
     }
diff --git a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
index 73ac344d..27425dff 100644
--- a/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
+++ b/core/src/test/java/io/github/tcdl/msb/impl/ResponderServerImplTest.java
@@ -21,12 +21,9 @@
 import java.util.Set;
 
 import static org.junit.Assert.*;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
+import static org.mockito.Matchers.*;
 import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.*;
 
 public class ResponderServerImplTest {
 
@@ -196,7 +193,7 @@ public void testCreateResponderWithResponseTopic() {
 
         ChannelManager mockChannelManager = mock(ChannelManager.class);
         Producer mockProducer = mock(Producer.class);
-        when(mockChannelManager.findOrCreateProducer(anyString(), any(RequestOptions.class))).thenReturn(mockProducer);
+        when(mockChannelManager.findOrCreateProducer(anyString(), eq(true), any(RequestOptions.class))).thenReturn(mockProducer);
         MsbContextImpl msbContext1 = new TestUtils.TestMsbContextBuilder()
                 .withChannelManager(mockChannelManager)
                 .build();
diff --git a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java
index 1a72f1b1..5a355111 100644
--- a/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java
+++ b/core/src/test/java/io/github/tcdl/msb/mock/adapterfactory/TestMsbAdapterFactory.java
@@ -28,7 +28,7 @@ public void init(MsbConfig msbConfig) {
     }
 
     @Override
-    public ProducerAdapter createProducerAdapter(String topic, RequestOptions requestOptions) {
+    public ProducerAdapter createProducerAdapter(String topic, boolean isResponseTopic, RequestOptions requestOptions) {
         TestMsbProducerAdapter producerAdapter = new TestMsbProducerAdapter(topic, storage);
         storage.addProducerAdapter(topic, producerAdapter);
         return producerAdapter;
@@ -42,7 +42,7 @@ public ConsumerAdapter createConsumerAdapter(String namespace, boolean isRespons
     }
 
     @Override
-    public ConsumerAdapter createConsumerAdapter(String topic, ResponderOptions responderOptions, boolean isResponseTopic) {
+    public ConsumerAdapter createConsumerAdapter(String topic, boolean isResponseTopic, ResponderOptions responderOptions) {
         ResponderOptions effectiveResponderOptions = responderOptions != null ? responderOptions: ResponderOptions.DEFAULTS;
         TestMsbConsumerAdapter consumerAdapter = new TestMsbConsumerAdapter(topic, storage);
         storage.addConsumerAdapter(topic, effectiveResponderOptions.getBindingKeys(), consumerAdapter);
diff --git a/pom.xml b/pom.xml
index 53b950df..ea3765fb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,6 +30,7 @@
     
     
         core
+        activemq
         amqp
         cli
         acceptance
@@ -126,6 +127,16 @@
                 amqp-client
                 5.4.0
             
+            
+                org.apache.activemq
+                activemq-client
+                5.15.8
+            
+            
+                org.apache.activemq
+                activemq-pool
+                5.15.8
+            
             
                 com.googlecode.junit-toolbox
                 junit-toolbox

From bcbd4eba82883f1139ea8433d54307df273c16cc Mon Sep 17 00:00:00 2001
From: Roman Drozdov 
Date: Wed, 24 Apr 2019 13:32:13 +0300
Subject: [PATCH 226/226] updated activemq client version

---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index ea3765fb..d4c6d7ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -130,12 +130,12 @@
             
                 org.apache.activemq
                 activemq-client
-                5.15.8
+                5.15.9
             
             
                 org.apache.activemq
                 activemq-pool
-                5.15.8
+                5.15.9
             
             
                 com.googlecode.junit-toolbox