future = rebindFutureReference.get();
+
+ if (!messageContainer.getAckInProgress().compareAndSet(false, true)) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Message container %s (XMLMessage %s) already has an ack in-progress",
+ messageContainer.getId(), messageContainer.getMessage().getMessageId()));
+ }
+ return future;
+ }
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format(
+ "Decrementing unacknowledged-messages counter for message container %s (XMLMessage %s).",
+ messageContainer.getId(), messageContainer.getMessage().getMessageId()));
+ }
+ unacknowledgedMessageTracker.decrement();
+
+ future.addCallback(newFlowReceiverContainerId -> {
+ messageContainer.setAcknowledged(true);
+ messageContainer.getAckInProgress().set(false);
+ }, e -> {
+ try {
+ if (!messageContainer.isStale()) {
+ logger.trace("Failed to rebind, re-incrementing unacknowledged-messages counter", e);
+ unacknowledgedMessageTracker.increment();
+ } else {
+ logger.trace("Failed to rebind", e);
+ }
+ } finally {
+ messageContainer.getAckInProgress().set(false);
+ }
+ });
+ return future;
}
public boolean isBound() {
@@ -450,6 +552,74 @@ public void setRebindWaitTimeout(long timeout, TimeUnit unit) {
this.rebindWaitTimeoutUnit = unit;
}
+ public long getRebindWaitTimeout(TimeUnit unit) {
+ return unit.convert(this.rebindWaitTimeout, this.rebindWaitTimeoutUnit);
+ }
+
+ public void pause() {
+ Lock writeLock = readWriteLock.writeLock();
+ writeLock.lock();
+ try {
+ logger.info(String.format("Pausing flow receiver container %s", id));
+ doFlowReceiverReferencePause();
+ isPaused.set(true);
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ /**
+ * CAUTION: DO NOT USE THIS. This is only exposed for testing. Use {@link #pause()} instead to pause
+ * the flow receiver container.
+ * @see #pause()
+ */
+ void doFlowReceiverReferencePause() {
+ Lock writeLock = readWriteLock.writeLock();
+ writeLock.lock();
+ try {
+ FlowReceiverReference flowReceiverReference = flowReceiverAtomicReference.get();
+ if (flowReceiverReference != null) {
+ flowReceiverReference.pause();
+ }
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ public void resume() throws JCSMPException {
+ Lock writeLock = readWriteLock.writeLock();
+ writeLock.lock();
+ try {
+ logger.info(String.format("Resuming flow receiver container %s", id));
+ doFlowReceiverReferenceResume();
+ isPaused.set(false);
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ /**
+ * CAUTION: DO NOT USE THIS. This is only exposed for testing. Use {@link #resume()} instead to resume
+ * the flow receiver container.
+ * @see #resume()
+ */
+ void doFlowReceiverReferenceResume() throws JCSMPException {
+ Lock writeLock = readWriteLock.writeLock();
+ writeLock.lock();
+ try {
+ FlowReceiverReference flowReceiverReference = flowReceiverAtomicReference.get();
+ if (flowReceiverReference != null) {
+ flowReceiverReference.resume();
+ }
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ public boolean isPaused() {
+ return isPaused.get();
+ }
+
/**
* Get the nested {@link FlowReceiver}.
* Caution: Instead of using this, consider instead implementing a new function with the required rebind
@@ -477,6 +647,10 @@ public String getQueueName() {
return queueName;
}
+ public XMLMessageMapper getXMLMessageMapper() {
+ return xmlMessageMapper;
+ }
+
static class FlowReceiverReference {
private final UUID id = UUID.randomUUID();
private final FlowReceiver flowReceiver;
@@ -506,6 +680,14 @@ public AtomicBoolean getStaleMessagesFlag() {
return staleMessagesFlag;
}
+ private void pause() {
+ flowReceiver.stop();
+ }
+
+ private void resume() throws JCSMPException {
+ flowReceiver.start();
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/JCSMPAcknowledgementCallbackFactory.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/JCSMPAcknowledgementCallbackFactory.java
deleted file mode 100644
index 2be5dcf6..00000000
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/JCSMPAcknowledgementCallbackFactory.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package com.solace.spring.cloud.stream.binder.util;
-
-import com.solacesystems.jcsmp.XMLMessage;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.integration.acks.AcknowledgmentCallback;
-import org.springframework.lang.Nullable;
-
-public class JCSMPAcknowledgementCallbackFactory {
- private final FlowReceiverContainer flowReceiverContainer;
- private final boolean hasTemporaryQueue;
- private final RetryableTaskService taskService;
- private ErrorQueueInfrastructure errorQueueInfrastructure;
-
- public JCSMPAcknowledgementCallbackFactory(FlowReceiverContainer flowReceiverContainer, boolean hasTemporaryQueue,
- RetryableTaskService taskService) {
- this.flowReceiverContainer = flowReceiverContainer;
- this.hasTemporaryQueue = hasTemporaryQueue;
- this.taskService = taskService;
- }
-
- public void setErrorQueueInfrastructure(ErrorQueueInfrastructure errorQueueInfrastructure) {
- this.errorQueueInfrastructure = errorQueueInfrastructure;
- }
-
- public AcknowledgmentCallback createCallback(MessageContainer messageContainer) {
- return new JCSMPAcknowledgementCallback(messageContainer, flowReceiverContainer, hasTemporaryQueue,
- taskService, errorQueueInfrastructure);
- }
-
- static class JCSMPAcknowledgementCallback implements AcknowledgmentCallback {
- private final MessageContainer messageContainer;
- private final FlowReceiverContainer flowReceiverContainer;
- private final boolean hasTemporaryQueue;
- private final ErrorQueueInfrastructure errorQueueInfrastructure;
- private final RetryableTaskService taskService;
- private boolean acknowledged = false;
- private boolean autoAckEnabled = true;
-
- private static final Log logger = LogFactory.getLog(JCSMPAcknowledgementCallback.class);
-
- JCSMPAcknowledgementCallback(MessageContainer messageContainer, FlowReceiverContainer flowReceiverContainer,
- boolean hasTemporaryQueue,
- RetryableTaskService taskService,
- @Nullable ErrorQueueInfrastructure errorQueueInfrastructure) {
- this.messageContainer = messageContainer;
- this.flowReceiverContainer = flowReceiverContainer;
- this.hasTemporaryQueue = hasTemporaryQueue;
- this.taskService = taskService;
- this.errorQueueInfrastructure = errorQueueInfrastructure;
- }
-
- @Override
- public void acknowledge(Status status) {
- // messageContainer.isAcknowledged() might be async set which is why we also need a local ack variable
- if (acknowledged || messageContainer.isAcknowledged()) {
- logger.info(String.format("%s %s is already acknowledged", XMLMessage.class.getSimpleName(),
- messageContainer.getMessage().getMessageId()));
- return;
- }
-
- try {
- switch (status) {
- case ACCEPT:
- flowReceiverContainer.acknowledge(messageContainer);
- break;
- case REJECT:
- if (republishToErrorQueue()) {
- break;
- } else if (!hasTemporaryQueue) {
- acknowledge(Status.REQUEUE);
- } else {
- logger.info(String.format(
- "Cannot %s %s %s since this flow is bound to a temporary queue, failed message " +
- "will be discarded",
- Status.REQUEUE, XMLMessage.class.getSimpleName(),
- messageContainer.getMessage().getMessageId()));
- flowReceiverContainer.acknowledge(messageContainer);
- }
- break;
- case REQUEUE:
- if (hasTemporaryQueue) {
- throw new UnsupportedOperationException(String.format(
- "Cannot %s XMLMessage %s, this operation is not supported with temporary queues",
- status, messageContainer.getMessage().getMessageId()));
- } else if (messageContainer.isStale()) {
- throw new SolaceStaleMessageException(String.format(
- "Message container %s (XMLMessage %s) is stale",
- messageContainer.getId(), messageContainer.getMessage().getMessageId()));
- } else {
- logger.info(String.format("%s %s: Will be re-queued onto queue %s",
- XMLMessage.class.getSimpleName(), messageContainer.getMessage().getMessageId(),
- flowReceiverContainer.getQueueName()));
- RetryableAckRebindTask rebindTask = new RetryableAckRebindTask(flowReceiverContainer,
- messageContainer, taskService);
- if (!rebindTask.run(0)) {
- taskService.submit(rebindTask);
- }
- }
- }
- } catch (SolaceAcknowledgmentException e) {
- throw e;
- } catch (Exception e) {
- throw new SolaceAcknowledgmentException(String.format("Failed to acknowledge XMLMessage %s",
- messageContainer.getMessage().getMessageId()), e);
- }
-
- acknowledged = true;
- }
-
- /**
- * Send the message to the error queue and acknowledge the message.
- * @return {@code true} if successful, {@code false} if {@code errorQueueInfrastructure} is not defined.
- */
- private boolean republishToErrorQueue() throws SolaceStaleMessageException {
- if (errorQueueInfrastructure == null) {
- return false;
- }
-
- logger.info(String.format("%s %s: Will be republished onto error queue %s",
- XMLMessage.class.getSimpleName(), messageContainer.getMessage().getMessageId(),
- errorQueueInfrastructure.getErrorQueueName()));
-
- if (messageContainer.isStale()) {
- throw new SolaceStaleMessageException(String.format("Cannot republish failed message container %s " +
- "(XMLMessage %s) to error queue %s. Message is stale and will be redelivered.",
- messageContainer.getId(), messageContainer.getMessage().getMessageId(),
- errorQueueInfrastructure.getErrorQueueName()));
- }
-
- errorQueueInfrastructure.createCorrelationKey(messageContainer, flowReceiverContainer, hasTemporaryQueue)
- .handleError(false);
- return true;
- }
-
- @Override
- public boolean isAcknowledged() {
- return acknowledged || messageContainer.isAcknowledged();
- }
-
- @Override
- public void noAutoAck() {
- autoAckEnabled = false;
- }
-
- @Override
- public boolean isAutoAck() {
- return autoAckEnabled;
- }
- }
-}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/JCSMPSessionProducerManager.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/JCSMPSessionProducerManager.java
index 0eecb437..b619cbdb 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/JCSMPSessionProducerManager.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/JCSMPSessionProducerManager.java
@@ -36,13 +36,16 @@ void close() {
sharedResource.close();
}
- static class CloudStreamEventHandler implements JCSMPStreamingPublishCorrelatingEventHandler {
+ public static class CloudStreamEventHandler implements JCSMPStreamingPublishCorrelatingEventHandler {
@Override
public void responseReceivedEx(Object correlationKey) {
if (correlationKey instanceof ErrorChannelSendingCorrelationKey) {
ErrorChannelSendingCorrelationKey key = (ErrorChannelSendingCorrelationKey) correlationKey;
- logger.debug("Producer received response for message " + StaticMessageHeaderAccessor.getId(key.getInputMessage()));
+ if (logger.isTraceEnabled()) {
+ logger.trace("Producer received response for message " +
+ StaticMessageHeaderAccessor.getId(key.getInputMessage()));
+ }
if (key.getConfirmCorrelation() != null) {
key.getConfirmCorrelation().success();
}
@@ -56,12 +59,11 @@ public void responseReceivedEx(Object correlationKey) {
" redelivered on the original queue.",
key.getSourceMessageId(), key.getErrorQueueName()), e);
}
- } else {
- logger.debug("Producer received response for correlation key: " + correlationKey);
+ } else if (logger.isTraceEnabled()) {
+ logger.trace("Producer received response for correlation key: " + correlationKey);
}
}
- @SuppressWarnings("ThrowableNotThrown")
@Override
public void handleErrorEx(Object correlationKey, JCSMPException cause, long timestamp) {
if (correlationKey instanceof ErrorChannelSendingCorrelationKey) {
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/MessageContainer.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/MessageContainer.java
index 0b39afe9..5460672f 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/MessageContainer.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/MessageContainer.java
@@ -12,6 +12,7 @@ public class MessageContainer {
private final BytesXMLMessage message;
private final UUID flowReceiverReferenceId;
private final AtomicBoolean staleFlag;
+ private final AtomicBoolean ackInProgress = new AtomicBoolean(false);
private boolean acknowledged;
MessageContainer(BytesXMLMessage message, UUID flowReceiverReferenceId, AtomicBoolean staleFlag) {
@@ -40,6 +41,10 @@ public boolean isStale() {
return staleFlag.get();
}
+ AtomicBoolean getAckInProgress() {
+ return ackInProgress;
+ }
+
void setAcknowledged(boolean acknowledged) {
this.acknowledged = acknowledged;
}
@@ -50,7 +55,9 @@ public String toString() {
.add("id=" + id)
.add("message=" + message)
.add("flowReceiverReferenceId=" + flowReceiverReferenceId)
+ .add("staleFlag=" + staleFlag)
.add("acknowledged=" + acknowledged)
+ .add("ackInProgress=" + ackInProgress)
.toString();
}
}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/RetryableAckRebindTask.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/RetryableAckRebindTask.java
index f3317516..217e7e7d 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/RetryableAckRebindTask.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/RetryableAckRebindTask.java
@@ -37,7 +37,7 @@ public boolean run(int attempt) throws InterruptedException {
if (messageContainer.isStale() && !flowReceiverContainer.isBound()) {
logger.warn(String.format(
"failed to rebind queue %s and flow container %s is now unbound. Attempting to bind.",
- flowReceiverContainer.getId(), flowReceiverContainer.getQueueName()), e);
+ flowReceiverContainer.getQueueName(), flowReceiverContainer.getId()), e);
taskService.submit(new RetryableBindTask(flowReceiverContainer));
return true;
} else {
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/RetryableTaskService.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/RetryableTaskService.java
index 8dc1d672..65d5cb9e 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/RetryableTaskService.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/RetryableTaskService.java
@@ -30,8 +30,8 @@ public void submit(RetryableTask task, long retryInterval, TimeUnit unit) {
}
if (!tasks.add(task)) {
- if (logger.isDebugEnabled()) {
- logger.debug(String.format("Skipping task submission. Task already exists: %s", task));
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Skipping task submission. Task already exists: %s", task));
}
return;
}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/SharedResourceManager.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/SharedResourceManager.java
index ae632c28..8aad1773 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/SharedResourceManager.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/SharedResourceManager.java
@@ -33,8 +33,8 @@ public T get(String key) throws Exception {
if (registeredIds.isEmpty()) {
logger.info(String.format("No %s exists, a new one will be created", type));
sharedResource = create();
- } else {
- logger.debug(String.format("A message %s already exists, reusing it", type));
+ } else if (logger.isTraceEnabled()) {
+ logger.trace(String.format("A message %s already exists, reusing it", type));
}
registeredIds.add(key);
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/SolaceBatchAcknowledgementException.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/SolaceBatchAcknowledgementException.java
new file mode 100644
index 00000000..7fe014b5
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/SolaceBatchAcknowledgementException.java
@@ -0,0 +1,23 @@
+package com.solace.spring.cloud.stream.binder.util;
+
+import java.util.Set;
+
+public class SolaceBatchAcknowledgementException extends SolaceAcknowledgmentException {
+ private final Set failedMessageIndexes;
+ private final boolean allStaleExceptions;
+
+ public SolaceBatchAcknowledgementException(Set failedMessageIndexes, boolean allStaleExceptions,
+ String message, Throwable cause) {
+ super(message, cause);
+ this.failedMessageIndexes = failedMessageIndexes;
+ this.allStaleExceptions = allStaleExceptions;
+ }
+
+ public Set getFailedMessageIndexes() {
+ return failedMessageIndexes;
+ }
+
+ public boolean isAllStaleExceptions() {
+ return allStaleExceptions;
+ }
+}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/SolaceFlowEventHandler.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/SolaceFlowEventHandler.java
new file mode 100644
index 00000000..79e67a4f
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/SolaceFlowEventHandler.java
@@ -0,0 +1,31 @@
+package com.solace.spring.cloud.stream.binder.util;
+
+import com.solacesystems.jcsmp.FlowEvent;
+import com.solacesystems.jcsmp.FlowEventArgs;
+import com.solacesystems.jcsmp.FlowEventHandler;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class SolaceFlowEventHandler implements FlowEventHandler {
+
+ private static final Log logger = LogFactory.getLog(SolaceFlowEventHandler.class);
+ private final XMLMessageMapper xmlMessageMapper;
+ private final String flowReceiverContainerId;
+
+ public SolaceFlowEventHandler(XMLMessageMapper xmlMessageMapper, String flowReceiverContainerId) {
+ this.xmlMessageMapper = xmlMessageMapper;
+ this.flowReceiverContainerId = flowReceiverContainerId;
+ }
+
+ @Override
+ public void handleEvent(Object o, FlowEventArgs flowEventArgs) {
+ if (flowEventArgs.getEvent() == FlowEvent.FLOW_RECONNECTED && xmlMessageMapper != null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Received flow event %s for flow receiver container %s. Will clear ignored properties.",
+ flowEventArgs.getEvent().name(), flowReceiverContainerId));
+ }
+ xmlMessageMapper.resetIgnoredProperties(flowReceiverContainerId);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/XMLMessageMapper.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/XMLMessageMapper.java
index d5000e85..0d5d107f 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/XMLMessageMapper.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/util/XMLMessageMapper.java
@@ -7,6 +7,7 @@
import com.solace.spring.cloud.stream.binder.messaging.SolaceBinderHeaderMeta;
import com.solace.spring.cloud.stream.binder.messaging.SolaceBinderHeaders;
import com.solace.spring.cloud.stream.binder.messaging.SolaceHeaderMeta;
+import com.solace.spring.cloud.stream.binder.messaging.SolaceHeaders;
import com.solace.spring.cloud.stream.binder.properties.SolaceConsumerProperties;
import com.solacesystems.common.util.ByteArray;
import com.solacesystems.jcsmp.BytesMessage;
@@ -26,35 +27,44 @@
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.StaticMessageHeaderAccessor;
import org.springframework.integration.acks.AcknowledgmentCallback;
+import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.support.MessageBuilder;
+import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.MimeType;
import org.springframework.util.SerializationUtils;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class XMLMessageMapper {
private static final Log logger = LogFactory.getLog(XMLMessageMapper.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ private static final MessageBuilderFactory MESSAGE_BUILDER_FACTORY = new DefaultMessageBuilderFactory();
static final int MESSAGE_VERSION = 1;
static final Encoder DEFAULT_ENCODING = Encoder.BASE64;
private final ObjectWriter stringSetWriter = OBJECT_MAPPER.writerFor(new TypeReference>(){});
private final ObjectReader stringSetReader = OBJECT_MAPPER.readerFor(new TypeReference>(){});
+ private final Set ignoredHeaderProperties = ConcurrentHashMap.newKeySet();
public BytesXMLMessage mapError(BytesXMLMessage inputMessage, SolaceConsumerProperties consumerProperties) {
BytesXMLMessage errorMessage = JCSMPFactory.onlyInstance().createMessage(inputMessage);
@@ -156,11 +166,42 @@ public XMLMessage map(Message> message, Collection excludedHeaders, bo
return xmlMessage;
}
- public Message> map(XMLMessage xmlMessage, AcknowledgmentCallback acknowledgmentCallback) throws SolaceMessageConversionException {
+ public Message> mapBatchMessage(List extends XMLMessage> xmlMessages,
+ AcknowledgmentCallback acknowledgmentCallback)
+ throws SolaceMessageConversionException {
+ return mapBatchMessage(xmlMessages, acknowledgmentCallback, false);
+ }
+
+ public Message> mapBatchMessage(List extends XMLMessage> xmlMessages,
+ AcknowledgmentCallback acknowledgmentCallback,
+ boolean setRawMessageHeader) throws SolaceMessageConversionException {
+ List> batchedHeaders = new ArrayList<>();
+ List batchedPayloads = new ArrayList<>();
+ for (XMLMessage xmlMessage : xmlMessages) {
+ Message> message = mapInternal(xmlMessage).build();
+ batchedHeaders.add(message.getHeaders());
+ batchedPayloads.add(message.getPayload());
+ }
+
+ AbstractIntegrationMessageBuilder> builder = MESSAGE_BUILDER_FACTORY.withPayload(batchedPayloads);
+ return injectRootMessageHeaders(builder, acknowledgmentCallback, setRawMessageHeader ? xmlMessages : null)
+ .setHeader(SolaceBinderHeaders.BATCHED_HEADERS, batchedHeaders)
+ .build();
+ }
+
+ public Message> map(XMLMessage xmlMessage, AcknowledgmentCallback acknowledgmentCallback)
+ throws SolaceMessageConversionException {
return map(xmlMessage, acknowledgmentCallback, false);
}
- public Message> map(XMLMessage xmlMessage, AcknowledgmentCallback acknowledgmentCallback, boolean setRawMessageHeader) throws SolaceMessageConversionException {
+ public Message> map(XMLMessage xmlMessage, AcknowledgmentCallback acknowledgmentCallback,
+ boolean setRawMessageHeader) throws SolaceMessageConversionException {
+ return injectRootMessageHeaders(mapInternal(xmlMessage), acknowledgmentCallback, setRawMessageHeader ?
+ xmlMessage : null).build();
+ }
+
+ private AbstractIntegrationMessageBuilder> mapInternal(XMLMessage xmlMessage)
+ throws SolaceMessageConversionException {
SDTMap metadata = xmlMessage.getProperties();
Object payload;
@@ -193,32 +234,59 @@ public Message> map(XMLMessage xmlMessage, AcknowledgmentCallback acknowledgme
throw exception;
}
- if (payload == null) {
- String msg = String.format("XMLMessage %s has no payload", xmlMessage.getMessageId());
- SolaceMessageConversionException exception = new SolaceMessageConversionException(msg);
- logger.warn(msg, exception);
- throw exception;
+ boolean isNullPayload = payload == null;
+ if (isNullPayload) {
+ //Set empty payload equivalent to null
+ if (xmlMessage instanceof BytesMessage) {
+ payload = new byte[0];
+ } else if (xmlMessage instanceof TextMessage || xmlMessage instanceof XMLContentMessage) {
+ payload = "";
+ } else if (xmlMessage instanceof MapMessage) {
+ payload = JCSMPFactory.onlyInstance().createMap();
+ } else if (xmlMessage instanceof StreamMessage) {
+ payload = JCSMPFactory.onlyInstance().createStream();
+ }
}
- MessageBuilder> builder = new DefaultMessageBuilderFactory()
+ AbstractIntegrationMessageBuilder> builder = MESSAGE_BUILDER_FACTORY
.withPayload(payload)
.copyHeaders(map(metadata))
- .setHeader(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, acknowledgmentCallback)
- .setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, xmlMessage.getHTTPContentType())
- .setHeaderIfAbsent(IntegrationMessageHeaderAccessor.DELIVERY_ATTEMPT, new AtomicInteger(0));
+ .setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, xmlMessage.getHTTPContentType());
+
+ if (isNullPayload) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Null payload detected, setting Spring header " + SolaceBinderHeaders.NULL_PAYLOAD);
+ }
+ builder.setHeader(SolaceBinderHeaders.NULL_PAYLOAD, isNullPayload);
+ }
for (Map.Entry> header : SolaceHeaderMeta.META.entrySet()) {
if (!header.getValue().isReadable()) {
continue;
}
- builder.setHeaderIfAbsent(header.getKey(), header.getValue().getReadAction().apply(xmlMessage));
+ if (ignoredHeaderProperties.contains(header.getKey())) {
+ continue;
+ }
+ try {
+ builder.setHeaderIfAbsent(header.getKey(), header.getValue().getReadAction().apply(xmlMessage));
+ } catch (UnsupportedOperationException e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Ignoring Solace header %s. Error: %s", header.getKey(), e.getMessage()), e);
+ }
+ ignoredHeaderProperties.add(header.getKey());
+ continue;
+ }
}
- if (setRawMessageHeader) {
- builder.setHeader(IntegrationMessageHeaderAccessor.SOURCE_DATA, xmlMessage);
- }
+ return builder;
+ }
- return builder.build();
+ private AbstractIntegrationMessageBuilder injectRootMessageHeaders(AbstractIntegrationMessageBuilder builder,
+ AcknowledgmentCallback acknowledgmentCallback,
+ Object sourceData) {
+ return builder.setHeader(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, acknowledgmentCallback)
+ .setHeaderIfAbsent(IntegrationMessageHeaderAccessor.DELIVERY_ATTEMPT, new AtomicInteger(0))
+ .setHeader(IntegrationMessageHeaderAccessor.SOURCE_DATA, sourceData);
}
SDTMap map(MessageHeaders headers, Collection excludedHeaders, boolean convertNonSerializableHeadersToString) {
@@ -346,6 +414,16 @@ private R rethrowableCall(ThrowingSupplier supplier) {
return supplier.get();
}
+ public void resetIgnoredProperties(String flowReceiverId) {
+ if (ignoredHeaderProperties.isEmpty()) {
+ return;
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Clearing ignored properties %s on flow receiver container %s", ignoredHeaderProperties, flowReceiverId));
+ }
+ ignoredHeaderProperties.clear();
+ }
+
@FunctionalInterface
private interface ThrowingFunction extends Function {
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/ITBase.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/ITBase.java
deleted file mode 100644
index 5fe572e1..00000000
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/ITBase.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package com.solace.spring.cloud.stream.binder;
-
-import com.solace.test.integration.semp.v2.SempV2Api;
-import com.solacesystems.jcsmp.JCSMPSession;
-import com.solacesystems.jcsmp.SpringJCSMPFactory;
-import org.assertj.core.api.SoftAssertions;
-import org.assertj.core.api.SoftAssertionsProvider;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.test.context.junit4.rules.SpringClassRule;
-import org.springframework.test.context.junit4.rules.SpringMethodRule;
-
-import java.util.concurrent.TimeUnit;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public abstract class ITBase {
- @ClassRule
- public static final SpringClassRule springClassRule = new SpringClassRule();
-
- @Rule
- public final SpringMethodRule springMethodRule = new SpringMethodRule();
-
- @Autowired
- private SpringJCSMPFactory springJCSMPFactory;
-
- @Value("${test.solace.mgmt.host:#{null}}")
- private String solaceMgmtHost;
-
- @Value("${test.solace.mgmt.username:#{null}}")
- private String solaceMgmtUsername;
-
- @Value("${test.solace.mgmt.password:#{null}}")
- private String solaceMgmtPassword;
-
- protected JCSMPSession jcsmpSession;
- protected SempV2Api sempV2Api;
-
- @Before
- public void setupSempV2Api() {
- assertThat(solaceMgmtHost).as("test.solace.mgmt.host cannot be blank").isNotBlank();
- assertThat(solaceMgmtUsername).as("test.solace.mgmt.username cannot be blank").isNotBlank();
- assertThat(solaceMgmtPassword).as("test.solace.mgmt.password cannot be blank").isNotBlank();
- sempV2Api = new SempV2Api(solaceMgmtHost, solaceMgmtUsername, solaceMgmtPassword);
- }
-
- @Before
- public void createJcsmpSession() throws Exception {
- jcsmpSession = springJCSMPFactory.createSession();
- jcsmpSession.connect();
- }
-
- @After
- public void closeJcsmpSession() {
- if (jcsmpSession != null && !jcsmpSession.isClosed()) {
- jcsmpSession.closeSession();
- }
- }
-
- public void retryAssert(SoftAssertionsProvider.ThrowingRunnable assertRun) throws InterruptedException {
- retryAssert(assertRun, 10, TimeUnit.SECONDS);
- }
-
- @SuppressWarnings("BusyWait")
- public void retryAssert(SoftAssertionsProvider.ThrowingRunnable assertRun, long timeout, TimeUnit unit)
- throws InterruptedException {
- final long expiry = System.currentTimeMillis() + unit.toMillis(timeout);
- SoftAssertions softAssertions;
- do {
- softAssertions = new SoftAssertions();
- softAssertions.check(assertRun);
- if (!softAssertions.wasSuccess()) {
- Thread.sleep(500);
- }
- } while (!softAssertions.wasSuccess() && System.currentTimeMillis() < expiry);
- softAssertions.assertAll();
- }
-}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/inbound/BatchCollectorTest.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/inbound/BatchCollectorTest.java
new file mode 100644
index 00000000..51fece10
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/inbound/BatchCollectorTest.java
@@ -0,0 +1,142 @@
+package com.solace.spring.cloud.stream.binder.inbound;
+
+import com.solace.spring.cloud.stream.binder.properties.SolaceConsumerProperties;
+import com.solace.spring.cloud.stream.binder.util.MessageContainer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static com.solace.spring.cloud.stream.binder.test.util.RetryableAssertions.RETRY_INTERVAL;
+import static com.solace.spring.cloud.stream.binder.test.util.RetryableAssertions.retryAssert;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+public class BatchCollectorTest {
+
+ @Test
+ public void testAddAndReceive() {
+ SolaceConsumerProperties consumerProperties = new SolaceConsumerProperties();
+ List messageContainers = IntStream.range(0, consumerProperties.getBatchMaxSize())
+ .mapToObj(i -> Mockito.mock(MessageContainer.class))
+ .collect(Collectors.toList());
+
+ BatchCollector batchCollector = new BatchCollector(consumerProperties);
+
+ assertThat(messageContainers).allSatisfy(messageContainer -> {
+ // check first since after the last added message, the asserts would fail
+ assertFalse(batchCollector.isBatchAvailable(), "batch is unexpectedly available");
+ assertThat(batchCollector.collectBatchIfAvailable()).isEmpty();
+ batchCollector.addToBatch(messageContainer);
+ });
+
+ assertTrue(batchCollector.isBatchAvailable(), "batch is not available");
+ assertThat(batchCollector.collectBatchIfAvailable()).get().asList()
+ .containsExactlyElementsOf(messageContainers);
+
+ // Do it again since we haven't confirmed delivery
+ assertTrue(batchCollector.isBatchAvailable(), "batch is not available");
+ assertThat(batchCollector.collectBatchIfAvailable()).get().asList()
+ .containsExactlyElementsOf(messageContainers);
+
+ batchCollector.confirmDelivery();
+ assertFalse(batchCollector.isBatchAvailable(), "batch is unexpectedly available");
+ assertThat(batchCollector.collectBatchIfAvailable()).isEmpty();
+ }
+
+ @Test
+ public void testReachedBatchTimeout(@Mock MessageContainer messageContainer) throws InterruptedException {
+ SolaceConsumerProperties consumerProperties = new SolaceConsumerProperties();
+ consumerProperties.setBatchTimeout((int) TimeUnit.SECONDS.toMillis(5));
+ BatchCollector batchCollector = new BatchCollector(consumerProperties);
+ batchCollector.addToBatch(messageContainer);
+ retryAssert(() -> {
+ assertTrue(batchCollector.isBatchAvailable(), "batch is not available");
+ assertThat(batchCollector.collectBatchIfAvailable()).get().asList().containsExactly(messageContainer);
+ }, consumerProperties.getBatchTimeout() + RETRY_INTERVAL.multipliedBy(2).toMillis(),
+ TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void testPruneStaleMessagesWhenAdding(@Mock MessageContainer messageContainer1,
+ @Mock MessageContainer messageContainer2,
+ @Mock MessageContainer messageContainer3,
+ @Mock MessageContainer messageContainer4) {
+ SolaceConsumerProperties consumerProperties = new SolaceConsumerProperties();
+ consumerProperties.setBatchMaxSize(2);
+
+ AtomicBoolean staleFlag = new AtomicBoolean(false);
+ UUID initialFlowReceiverReferenceId = UUID.randomUUID();
+ Mockito.when(messageContainer1.getFlowReceiverReferenceId()).thenReturn(initialFlowReceiverReferenceId);
+ Mockito.when(messageContainer2.getFlowReceiverReferenceId()).thenReturn(initialFlowReceiverReferenceId);
+ Mockito.when(messageContainer1.isStale()).thenAnswer((Answer) invocation -> staleFlag.get());
+ Mockito.when(messageContainer2.isStale()).thenAnswer((Answer) invocation -> staleFlag.get());
+
+ UUID newFlowReceiverReferenceId = UUID.randomUUID();
+ Mockito.when(messageContainer3.getFlowReceiverReferenceId()).thenReturn(newFlowReceiverReferenceId);
+ Mockito.when(messageContainer4.getFlowReceiverReferenceId()).thenReturn(newFlowReceiverReferenceId);
+
+ BatchCollector batchCollector = new BatchCollector(consumerProperties);
+
+ batchCollector.addToBatch(messageContainer1);
+ batchCollector.addToBatch(messageContainer2);
+ assertTrue(batchCollector.isBatchAvailable(), "batch is not available");
+ assertThat(batchCollector.collectBatchIfAvailable()).get().asList()
+ .containsExactly(messageContainer1, messageContainer2);
+
+ staleFlag.set(true);
+ batchCollector.addToBatch(messageContainer3);
+ assertFalse(batchCollector.isBatchAvailable(), "batch is unexpectedly available");
+ assertThat(batchCollector.collectBatchIfAvailable()).isEmpty();
+
+ batchCollector.addToBatch(messageContainer4);
+ assertTrue(batchCollector.isBatchAvailable(), "batch is not available");
+ assertThat(batchCollector.collectBatchIfAvailable()).get().asList()
+ .containsExactly(messageContainer3, messageContainer4);
+ }
+
+ @ParameterizedTest(name = "[{index}] testTimeout={0}")
+ @ValueSource(booleans = {false, true})
+ public void testPruneStaleMessagesAfterTimeout(boolean testTimeout,
+ @Mock MessageContainer messageContainer1,
+ @Mock MessageContainer messageContainer2) throws Exception {
+ Mockito.when(messageContainer1.getFlowReceiverReferenceId()).thenReturn(UUID.randomUUID());
+ Mockito.when(messageContainer1.isStale()).thenReturn(true);
+ Mockito.when(messageContainer2.getFlowReceiverReferenceId()).thenReturn(UUID.randomUUID());
+
+ SolaceConsumerProperties consumerProperties = new SolaceConsumerProperties();
+ if (testTimeout) {
+ consumerProperties.setBatchTimeout(1000);
+ } else {
+ consumerProperties.setBatchMaxSize(1);
+ consumerProperties.setBatchTimeout(0);
+ }
+
+ BatchCollector batchCollector = new BatchCollector(consumerProperties);
+
+ batchCollector.addToBatch(messageContainer1);
+ if (testTimeout) {
+ assertFalse(batchCollector.isBatchAvailable(), "batch is unexpectedly available");
+ Thread.sleep(consumerProperties.getBatchTimeout() + 1000);
+ }
+ assertThat(batchCollector.isBatchAvailable()).isEqualTo(testTimeout);
+ assertThat(batchCollector.collectBatchIfAvailable()).isEmpty();
+
+ batchCollector.addToBatch(messageContainer2);
+ assertTrue(batchCollector.isBatchAvailable(), "batch is not available");
+ assertThat(batchCollector.collectBatchIfAvailable()).get().asList().containsExactly(messageContainer2);
+ }
+}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/inbound/acknowledge/JCSMPAcknowledgementCallbackIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/inbound/acknowledge/JCSMPAcknowledgementCallbackIT.java
new file mode 100644
index 00000000..d4722e29
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/inbound/acknowledge/JCSMPAcknowledgementCallbackIT.java
@@ -0,0 +1,744 @@
+package com.solace.spring.cloud.stream.binder.inbound.acknowledge;
+
+import com.solace.spring.boot.autoconfigure.SolaceJavaAutoConfiguration;
+import com.solace.spring.cloud.stream.binder.properties.SolaceConsumerProperties;
+import com.solace.spring.cloud.stream.binder.util.ErrorQueueInfrastructure;
+import com.solace.spring.cloud.stream.binder.util.FlowReceiverContainer;
+import com.solace.spring.cloud.stream.binder.util.JCSMPSessionProducerManager;
+import com.solace.spring.cloud.stream.binder.util.MessageContainer;
+import com.solace.spring.cloud.stream.binder.util.RetryableAckRebindTask;
+import com.solace.spring.cloud.stream.binder.util.RetryableRebindTask;
+import com.solace.spring.cloud.stream.binder.util.RetryableTaskService;
+import com.solace.spring.cloud.stream.binder.util.SolaceAcknowledgmentException;
+import com.solace.spring.cloud.stream.binder.util.SolaceBatchAcknowledgementException;
+import com.solace.spring.cloud.stream.binder.util.SolaceStaleMessageException;
+import com.solace.spring.cloud.stream.binder.util.UnboundFlowReceiverContainerException;
+import com.solace.test.integration.junit.jupiter.extension.ExecutorServiceExtension;
+import com.solace.test.integration.junit.jupiter.extension.ExecutorServiceExtension.ExecSvc;
+import com.solace.test.integration.junit.jupiter.extension.PubSubPlusExtension;
+import com.solace.test.integration.semp.v2.SempV2Api;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnQueue;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnQueueMsg;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnQueueMsgsResponse;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorSempMeta;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorSempPaging;
+import com.solacesystems.jcsmp.EndpointProperties;
+import com.solacesystems.jcsmp.JCSMPException;
+import com.solacesystems.jcsmp.JCSMPFactory;
+import com.solacesystems.jcsmp.JCSMPProperties;
+import com.solacesystems.jcsmp.JCSMPSession;
+import com.solacesystems.jcsmp.Queue;
+import com.solacesystems.jcsmp.TextMessage;
+import com.solacesystems.jcsmp.XMLMessageProducer;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.function.ThrowingRunnable;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.junitpioneer.jupiter.cartesian.CartesianTest;
+import org.junitpioneer.jupiter.cartesian.CartesianTest.Values;
+import org.mockito.Mockito;
+import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
+import org.springframework.integration.acks.AcknowledgmentCallback;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import static com.solace.spring.cloud.stream.binder.test.util.RetryableAssertions.retryAssert;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SpringJUnitConfig(classes = SolaceJavaAutoConfiguration.class,
+ initializers = ConfigDataApplicationContextInitializer.class)
+@ExtendWith(ExecutorServiceExtension.class)
+@ExtendWith(PubSubPlusExtension.class)
+@Timeout(value = 1, unit = TimeUnit.MINUTES)
+public class JCSMPAcknowledgementCallbackIT {
+ private RetryableTaskService retryableTaskService;
+ private final AtomicReference flowReceiverContainerReference = new AtomicReference<>();
+ private XMLMessageProducer producer;
+ private String vpnName;
+ private Runnable closeErrorQueueInfrastructureCallback;
+
+ private static final Log logger = LogFactory.getLog(JCSMPAcknowledgementCallbackIT.class);
+
+ @BeforeEach
+ public void setup(JCSMPSession jcsmpSession) throws Exception {
+ retryableTaskService = Mockito.spy(new RetryableTaskService());
+ producer = jcsmpSession.getMessageProducer(new JCSMPSessionProducerManager.CloudStreamEventHandler());
+ vpnName = (String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME);
+ }
+
+ @AfterEach
+ public void cleanup() {
+ if (producer != null) {
+ producer.close();
+ }
+
+ if (closeErrorQueueInfrastructureCallback != null) {
+ closeErrorQueueInfrastructureCallback.run();
+ }
+ }
+
+ @CartesianTest(name = "[{index}] numMessages={0}, isDurable={1}")
+ public void testAccept(@Values(ints = {1, 255}) int numMessages,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api)
+ throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, !isDurable, retryableTaskService);
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.ACCEPT);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), 0);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+ }
+
+ @CartesianTest(name = "[{index}] numMessages={0}, isDurable={1}")
+ public void testReject(@Values(ints = {1, 255}) int numMessages,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api)
+ throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, !isDurable, retryableTaskService);
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REJECT);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+
+ if (isDurable) {
+ // Message was redelivered
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), numMessages);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 2);
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ } else {
+ // Message was discarded
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), 0);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+ }
+ }
+
+ @ParameterizedTest(name = "[{index}] numMessages={0}")
+ @ValueSource(ints = {1, 255})
+ public void testRejectFail(int numMessages,
+ JCSMPSession jcsmpSession,
+ Queue queue,
+ SempV2Api sempV2Api) throws Exception {
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, !queue.isDurable(), retryableTaskService);
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ logger.info(String.format("Disabling egress for queue %s", queue.getName()));
+ sempV2Api.config().updateMsgVpnQueue((String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
+ queue.getName(), new ConfigMsgVpnQueue().egressEnabled(false), null);
+ retryAssert(() -> assertFalse(sempV2Api.monitor()
+ .getMsgVpnQueue(vpnName, queue.getName(), null)
+ .getData()
+ .isEgressEnabled()));
+
+ logger.info("Acknowledging messages");
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REJECT);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ Thread.sleep(TimeUnit.SECONDS.toMillis(3));
+
+ logger.info(String.format("Verifying %s message containers haven't been ack'd", numMessages));
+ assertThat(messageContainers).allSatisfy(messageContainer -> {
+ assertThat(messageContainer.isAcknowledged()).isFalse();
+ assertThat(messageContainer.isStale()).isTrue();
+ });
+
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+ assertThat(sempV2Api.monitor()
+ .getMsgVpnQueue(vpnName, queue.getName(), null)
+ .getData()
+ .getBindRequestCount())
+ .isGreaterThan(1);
+
+ logger.info(String.format("Enabling egress for queue %s", queue.getName()));
+ sempV2Api.config().updateMsgVpnQueue((String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
+ queue.getName(), new ConfigMsgVpnQueue().egressEnabled(true), null);
+ retryAssert(() -> assertTrue(sempV2Api.monitor()
+ .getMsgVpnQueue(vpnName, queue.getName(), null)
+ .getData()
+ .isEgressEnabled()));
+ Thread.sleep(TimeUnit.SECONDS.toMillis(5)); // rebind task retry interval
+
+ // Message was redelivered
+ logger.info("Verifying message was redelivered");
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), numMessages);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 2);
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ }
+
+ @CartesianTest(name = "[{index}] numMessages={0}, isDurable={1}")
+ public void testRejectWithErrorQueue(@Values(ints = {1, 255}) int numMessages,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, !isDurable, retryableTaskService);
+
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ ErrorQueueInfrastructure errorQueueInfrastructure = initializeErrorQueueInfrastructure(jcsmpSession,
+ acknowledgementCallbackFactory);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REJECT);
+
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), 0);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+ validateNumEnqueuedMessages(sempV2Api, errorQueueInfrastructure.getErrorQueueName(), numMessages);
+ validateNumRedeliveredMessages(sempV2Api, errorQueueInfrastructure.getErrorQueueName(), 0);
+ }
+
+ @CartesianTest(name = "[{index}] numMessages={0}, isDurable={1}")
+ public void testRejectWithErrorQueueFail(@Values(ints = {1, 255}) int numMessages,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, !isDurable, retryableTaskService);
+
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ ErrorQueueInfrastructure errorQueueInfrastructure = initializeErrorQueueInfrastructure(jcsmpSession,
+ acknowledgementCallbackFactory);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ logger.info(String.format("Disabling ingress for error queue %s",
+ errorQueueInfrastructure.getErrorQueueName()));
+ sempV2Api.config().updateMsgVpnQueue((String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
+ errorQueueInfrastructure.getErrorQueueName(), new ConfigMsgVpnQueue().ingressEnabled(false), null);
+ retryAssert(() -> assertFalse(sempV2Api.monitor()
+ .getMsgVpnQueue(vpnName, errorQueueInfrastructure.getErrorQueueName(), null)
+ .getData()
+ .isIngressEnabled()));
+
+ logger.info("Acknowledging messages");
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REJECT);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+
+ if (isDurable) {
+ // Message was redelivered
+ logger.info("Verifying message was redelivered");
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), numMessages);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 2);
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ } else {
+ // Message was discarded
+ logger.info("Verifying message was discarded");
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), 0);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+ }
+
+ validateNumEnqueuedMessages(sempV2Api, errorQueueInfrastructure.getErrorQueueName(), 0);
+ validateNumRedeliveredMessages(sempV2Api, errorQueueInfrastructure.getErrorQueueName(), 0);
+ }
+
+ @CartesianTest(name = "[{index}] numMessages={0}, isDurable={1}")
+ public void testRequeue(@Values(ints = {1, 255}) int numMessages,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, !isDurable, retryableTaskService);
+
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ if (isDurable) {
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REQUEUE);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), numMessages);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 2);
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ } else {
+ SolaceAcknowledgmentException thrown = assertThrows(SolaceAcknowledgmentException.class,
+ () -> acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REQUEUE));
+ assertThat(acknowledgmentCallback.isAcknowledged()).isFalse();
+ assertThat(thrown).hasRootCauseInstanceOf(UnsupportedOperationException.class);
+ assertThat(ExceptionUtils.getRootCause(thrown.getCause()))
+ .hasMessageContaining("not supported with temporary queues");
+ }
+ }
+
+ @ParameterizedTest(name = "[{index}] numMessages={0}")
+ @ValueSource(ints = {1, 255})
+ public void testRequeueFail(int numMessages, JCSMPSession jcsmpSession, Queue queue, SempV2Api sempV2Api)
+ throws Exception {
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, false, retryableTaskService);
+
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ logger.info(String.format("Disabling egress for queue %s", queue.getName()));
+ sempV2Api.config().updateMsgVpnQueue((String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
+ queue.getName(), new ConfigMsgVpnQueue().egressEnabled(false), null);
+ retryAssert(() -> assertFalse(sempV2Api.monitor()
+ .getMsgVpnQueue(vpnName, queue.getName(), null)
+ .getData()
+ .isEgressEnabled()));
+
+ logger.info("Acknowledging messages");
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REQUEUE);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ Thread.sleep(TimeUnit.SECONDS.toMillis(3));
+
+ logger.info(String.format("Verifying %s message containers haven't been ack'd", numMessages));
+ assertThat(messageContainers).allSatisfy(messageContainer -> {
+ assertThat(messageContainer.isAcknowledged()).isFalse();
+ assertThat(messageContainer.isStale()).isTrue();
+ });
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+ assertThat(sempV2Api.monitor()
+ .getMsgVpnQueue(vpnName, queue.getName(), null)
+ .getData()
+ .getBindRequestCount())
+ .isGreaterThan(1);
+
+ logger.info(String.format("Enabling egress for queue %s", queue.getName()));
+ sempV2Api.config().updateMsgVpnQueue((String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
+ queue.getName(), new ConfigMsgVpnQueue().egressEnabled(true), null);
+ retryAssert(() -> assertTrue(sempV2Api.monitor()
+ .getMsgVpnQueue(vpnName, queue.getName(), null)
+ .getData()
+ .isEgressEnabled()));
+ Thread.sleep(TimeUnit.SECONDS.toMillis(5)); // rebind task retry interval
+
+ // Message was redelivered
+ logger.info("Verifying message was redelivered");
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), numMessages);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 2);
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ }
+
+ @ParameterizedTest(name = "[{index}] numMessages={0}")
+ @ValueSource(ints = {1, 255})
+ public void testRequeueDelegateWhileRebinding(int numMessages,
+ JCSMPSession jcsmpSession,
+ Queue queue,
+ SempV2Api sempV2Api,
+ @ExecSvc(poolSize = 1) ExecutorService executorService)
+ throws Exception {
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, false, retryableTaskService);
+
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ MessageContainer blockingContainer = sendAndReceiveMessages(queue, flowReceiverContainer, 1).get(0);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ Future rebindFuture = executorService.submit(() ->
+ flowReceiverContainer.rebind(messageContainers.get(0).getFlowReceiverReferenceId()));
+ Thread.sleep(1000);
+ assertFalse(rebindFuture.isDone());
+
+ for (MessageContainer messageContainer : messageContainers) {
+ // force async rebind
+ Mockito.doReturn(null).doCallRealMethod().when(flowReceiverContainer)
+ .acknowledgeRebind(messageContainer, true);
+ }
+
+ logger.info(String.format("Acknowledging %s message containers", messageContainers.size()));
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REQUEUE);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+
+ if (acknowledgmentCallback instanceof JCSMPBatchAcknowledgementCallback) {
+ Mockito.verify(retryableTaskService, Mockito.times(2)).submit(
+ new RetryableRebindTask(flowReceiverContainer,
+ messageContainers.get(0).getFlowReceiverReferenceId(), retryableTaskService));
+ } else {
+ for (MessageContainer messageContainer : messageContainers) {
+ Mockito.verify(retryableTaskService).submit(
+ new RetryableAckRebindTask(flowReceiverContainer, messageContainer, retryableTaskService));
+ }
+ }
+
+ Thread.sleep(TimeUnit.SECONDS.toMillis(3));
+
+ logger.info(String.format("Verifying %s message containers haven't been ack'd", messageContainers.size()));
+ assertThat(messageContainers).allSatisfy(messageContainer -> {
+ assertThat(messageContainer.isAcknowledged())
+ .isEqualTo(acknowledgmentCallback instanceof JCSMPAcknowledgementCallback ||
+ messageContainer.equals(messageContainers.get(messageContainers.size() - 1)));
+ assertThat(messageContainer.isStale()).isFalse();
+ });
+ assertFalse(rebindFuture.isDone());
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages + 1);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+
+ logger.info("Acknowledging blocking message");
+ flowReceiverContainer.acknowledge(blockingContainer);
+ Thread.sleep(TimeUnit.SECONDS.toMillis(5)); // rebind task retry interval
+ assertNotNull(rebindFuture.get(1, TimeUnit.MINUTES));
+
+ // Message was redelivered
+ logger.info("Verifying message was redelivered");
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), numMessages);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 2);
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ }
+
+ @CartesianTest(name = "[{index}] numMessages={0}, isDurable={1}")
+ public void testReAckAfterAccept(@Values(ints = {1, 255}) int numMessages,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api) throws Throwable {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, !isDurable, retryableTaskService);
+
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ ThrowingRunnable verifyExpectedState = () -> {
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), 0);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+ };
+
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.ACCEPT);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ verifyExpectedState.run();
+
+ for (AcknowledgmentCallback.Status status : AcknowledgmentCallback.Status.values()) {
+ acknowledgmentCallback.acknowledge(status);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ verifyExpectedState.run();
+ }
+ }
+
+ @CartesianTest(name = "[{index}] numMessages={0}, isDurable={1}")
+ public void testReAckAfterReject(@Values(ints = {1, 255}) int numMessages,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api) throws Throwable {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, !isDurable, retryableTaskService);
+
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ ThrowingRunnable verifyExpectedState = () -> {
+ if (isDurable) {
+ // Message was redelivered
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), numMessages);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 2);
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ } else {
+ // Message was discarded
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), 0);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+ }
+ };
+
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REJECT);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ verifyExpectedState.run();
+
+ for (AcknowledgmentCallback.Status status : AcknowledgmentCallback.Status.values()) {
+ acknowledgmentCallback.acknowledge(status);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ verifyExpectedState.run();
+ }
+ }
+
+ @CartesianTest(name = "[{index}] numMessages={0}, isDurable={1}")
+ public void testReAckAfterRejectWithErrorQueue(@Values(ints = {1, 255}) int numMessages,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api) throws Throwable {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, !isDurable, retryableTaskService);
+
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ ErrorQueueInfrastructure errorQueueInfrastructure = initializeErrorQueueInfrastructure(jcsmpSession,
+ acknowledgementCallbackFactory);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ ThrowingRunnable verifyExpectedState = () -> {
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), 0);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+ validateNumEnqueuedMessages(sempV2Api, errorQueueInfrastructure.getErrorQueueName(), numMessages);
+ validateNumRedeliveredMessages(sempV2Api, errorQueueInfrastructure.getErrorQueueName(), 0);
+ };
+
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REJECT);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ verifyExpectedState.run();
+
+ for (AcknowledgmentCallback.Status status : AcknowledgmentCallback.Status.values()) {
+ acknowledgmentCallback.acknowledge(status);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ verifyExpectedState.run();
+ }
+ }
+
+ @ParameterizedTest(name = "[{index}] numMessages={0}")
+ @ValueSource(ints = {1, 255})
+ public void testReAckAfterRequeue(int numMessages,
+ JCSMPSession jcsmpSession,
+ Queue queue,
+ SempV2Api sempV2Api) throws Throwable {
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, false, retryableTaskService);
+
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ ThrowingRunnable verifyExpectedState = () -> {
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), numMessages);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 2);
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ };
+
+ acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REQUEUE);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ verifyExpectedState.run();
+
+ for (AcknowledgmentCallback.Status status : AcknowledgmentCallback.Status.values()) {
+ acknowledgmentCallback.acknowledge(status);
+ assertThat(acknowledgmentCallback.isAcknowledged()).isTrue();
+ verifyExpectedState.run();
+ }
+ }
+
+ @CartesianTest(name = "[{index}] numMessages={0}, isDurable={1}, createErrorQueue={2}")
+ public void testAckStaleMessage(@Values(ints = {1, 255}) int numMessages,
+ @Values(booleans = {false, true}) boolean isDurable,
+ @Values(booleans = {false, true}) boolean createErrorQueue,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = initializeFlowReceiverContainer(jcsmpSession, queue);
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory = new JCSMPAcknowledgementCallbackFactory(
+ flowReceiverContainer, !isDurable, retryableTaskService);
+
+ List messageContainers = sendAndReceiveMessages(queue, flowReceiverContainer, numMessages)
+ .stream()
+ .map(Mockito::spy)
+ .peek(m -> Mockito.when(m.isStale()).thenReturn(true))
+ .collect(Collectors.toList());
+ Optional errorQueueName = Optional.of(createErrorQueue)
+ .filter(e -> e)
+ .map(c -> initializeErrorQueueInfrastructure(jcsmpSession, acknowledgementCallbackFactory))
+ .map(ErrorQueueInfrastructure::getErrorQueueName);
+ AcknowledgmentCallback acknowledgmentCallback = createAcknowledgmentCallback(acknowledgementCallbackFactory,
+ messageContainers);
+
+ for (AcknowledgmentCallback.Status status : AcknowledgmentCallback.Status.values()) {
+ SolaceAcknowledgmentException exception = assertThrows(SolaceAcknowledgmentException.class,
+ () -> acknowledgmentCallback.acknowledge(status));
+ Class extends Throwable> expectedRootCause = !isDurable &&
+ status.equals(AcknowledgmentCallback.Status.REQUEUE) ? UnsupportedOperationException.class :
+ SolaceStaleMessageException.class;
+
+ assertThat(acknowledgmentCallback.isAcknowledged())
+ .describedAs("Unexpected ack state for %s re-ack", status)
+ .isFalse();
+ assertThat(exception)
+ .describedAs("Unexpected root cause for %s re-ack", status)
+ .hasRootCauseInstanceOf(expectedRootCause);
+ if (exception instanceof SolaceBatchAcknowledgementException) {
+ assertThat(((SolaceBatchAcknowledgementException) exception).isAllStaleExceptions())
+ .describedAs("Unexpected stale batch state for %s re-ack", status)
+ .isEqualTo(expectedRootCause.equals(SolaceStaleMessageException.class));
+ }
+ validateNumEnqueuedMessages(sempV2Api, queue.getName(), numMessages);
+ validateNumRedeliveredMessages(sempV2Api, queue.getName(), 0);
+ validateQueueBindSuccesses(sempV2Api, queue.getName(), 1);
+ if (errorQueueName.isPresent()) {
+ validateNumEnqueuedMessages(sempV2Api, errorQueueName.get(), 0);
+ validateNumRedeliveredMessages(sempV2Api, errorQueueName.get(), 0);
+ }
+ }
+ }
+
+ private List sendAndReceiveMessages(Queue queue,
+ FlowReceiverContainer flowReceiverContainer,
+ int numMessages)
+ throws JCSMPException, UnboundFlowReceiverContainerException {
+ assertThat(numMessages).isGreaterThan(0);
+
+ for (int i = 0; i < numMessages; i++) {
+ producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
+ }
+
+ if (numMessages > 1) {
+ List messageContainers = new ArrayList<>();
+ for (int i = 0; i < numMessages; i++) {
+ MessageContainer messageContainer = flowReceiverContainer.receive((int)
+ TimeUnit.SECONDS.toMillis(10));
+ assertNotNull(messageContainer);
+ messageContainers.add(messageContainer);
+ }
+ return messageContainers;
+ } else {
+ MessageContainer messageContainer = flowReceiverContainer.receive((int)
+ TimeUnit.SECONDS.toMillis(10));
+ assertNotNull(messageContainer);
+ return Collections.singletonList(messageContainer);
+ }
+ }
+
+ private AcknowledgmentCallback createAcknowledgmentCallback(
+ JCSMPAcknowledgementCallbackFactory acknowledgementCallbackFactory,
+ List messageContainers) {
+ assertThat(messageContainers).hasSizeGreaterThan(0);
+ if (messageContainers.size() > 1) {
+ return acknowledgementCallbackFactory.createBatchCallback(messageContainers);
+ } else {
+ return acknowledgementCallbackFactory.createCallback(messageContainers.get(0));
+ }
+ }
+
+ private FlowReceiverContainer initializeFlowReceiverContainer(JCSMPSession jcsmpSession, Queue queue)
+ throws JCSMPException {
+ if (flowReceiverContainerReference.compareAndSet(null, Mockito.spy(new FlowReceiverContainer(
+ jcsmpSession, queue.getName(), new EndpointProperties())))) {
+ flowReceiverContainerReference.get().bind();
+ }
+ return flowReceiverContainerReference.get();
+ }
+
+ private ErrorQueueInfrastructure initializeErrorQueueInfrastructure(JCSMPSession jcsmpSession,
+ JCSMPAcknowledgementCallbackFactory ackCallbackFactory) {
+ if (closeErrorQueueInfrastructureCallback != null) {
+ throw new IllegalStateException("Should only have one error queue infrastructure");
+ }
+
+ String producerManagerKey = UUID.randomUUID().toString();
+ JCSMPSessionProducerManager jcsmpSessionProducerManager = new JCSMPSessionProducerManager(jcsmpSession);
+ ErrorQueueInfrastructure errorQueueInfrastructure = new ErrorQueueInfrastructure(jcsmpSessionProducerManager,
+ producerManagerKey, RandomStringUtils.randomAlphanumeric(20), new SolaceConsumerProperties(),
+ new RetryableTaskService());
+ Queue errorQueue = JCSMPFactory.onlyInstance().createQueue(errorQueueInfrastructure.getErrorQueueName());
+ ackCallbackFactory.setErrorQueueInfrastructure(errorQueueInfrastructure);
+ closeErrorQueueInfrastructureCallback = () -> {
+ jcsmpSessionProducerManager.release(producerManagerKey);
+
+ try {
+ jcsmpSession.deprovision(errorQueue, JCSMPSession.WAIT_FOR_CONFIRM);
+ } catch (JCSMPException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ try {
+ jcsmpSession.provision(errorQueue, new EndpointProperties(), JCSMPSession.WAIT_FOR_CONFIRM);
+ } catch (JCSMPException e) {
+ throw new RuntimeException(e);
+ }
+
+ return errorQueueInfrastructure;
+ }
+
+ private void validateNumEnqueuedMessages(SempV2Api sempV2Api, String queueName, int expectedCount)
+ throws InterruptedException {
+ retryAssert(() -> {
+ List messages = new ArrayList<>();
+ Optional cursor = Optional.empty();
+ do {
+ MonitorMsgVpnQueueMsgsResponse response = sempV2Api.monitor()
+ .getMsgVpnQueueMsgs(vpnName, queueName, Integer.MAX_VALUE, cursor.orElse(null),
+ null, null);
+ cursor = Optional.ofNullable(response.getMeta())
+ .map(MonitorSempMeta::getPaging)
+ .map(MonitorSempPaging::getCursorQuery);
+ messages.addAll(response.getData());
+ } while (cursor.isPresent());
+ assertThat(messages).hasSize(expectedCount);
+ });
+ }
+
+ private void validateNumRedeliveredMessages(SempV2Api sempV2Api, String queueName, int expectedCount)
+ throws InterruptedException {
+ retryAssert(() -> assertThat(sempV2Api.monitor()
+ .getMsgVpnQueue(vpnName, queueName, null)
+ .getData()
+ .getRedeliveredMsgCount())
+ .isEqualTo(expectedCount));
+ }
+
+ private void validateQueueBindSuccesses(SempV2Api sempV2Api, String queueName, int expectedCount)
+ throws InterruptedException {
+ retryAssert(() -> assertThat(sempV2Api.monitor()
+ .getMsgVpnQueue(vpnName, queueName, null)
+ .getData()
+ .getBindSuccessCount())
+ .isEqualTo(expectedCount));
+ }
+}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/messaging/SolaceHeadersTest.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/messaging/SolaceHeadersTest.java
index b2b6aaf7..0f11e0f8 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/messaging/SolaceHeadersTest.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/messaging/SolaceHeadersTest.java
@@ -1,22 +1,22 @@
package com.solace.spring.cloud.stream.binder.messaging;
+import com.solace.spring.cloud.stream.binder.test.junit.param.provider.SolaceSpringHeaderArgumentsProvider;
import com.solacesystems.jcsmp.Destination;
import com.solacesystems.jcsmp.JCSMPFactory;
import com.solacesystems.jcsmp.TextMessage;
import com.solacesystems.jcsmp.XMLMessage;
-import org.apache.commons.lang.RandomStringUtils;
-import org.apache.commons.lang.math.RandomUtils;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+import org.mockito.Mockito;
import org.springframework.messaging.MessageHeaders;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
@@ -33,40 +33,23 @@
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.CoreMatchers.startsWithIgnoringCase;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-@RunWith(Parameterized.class)
-public class SolaceHeadersTest {
- @Parameterized.Parameter
- public String parameterSetName; // Only used for parameter set naming
-
- @Parameterized.Parameter(1)
- public Class> headersClass;
-
- @Parameterized.Parameter(2)
- public Map> headersMeta;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+public class SolaceHeadersTest {
private static final Log logger = LogFactory.getLog(SolaceHeadersTest.class);
- @Parameterized.Parameters(name = "{0}")
- public static Collection> headerSets() {
- return Arrays.asList(new Object[][]{
- {SolaceHeaders.class.getSimpleName(), SolaceHeaders.class, SolaceHeaderMeta.META},
- {SolaceBinderHeaders.class.getSimpleName(), SolaceBinderHeaders.class, SolaceBinderHeaderMeta.META}
- });
- }
-
- @Test
- public void testPrefix() throws Exception {
- Field field = getPrefixField();
- assertEquals(String.format("%s is not a String", field.getName()), String.class, field.getType());
- assertTrue(String.format("%s is not static", field.getName()), Modifier.isStatic(field.getModifiers()));
- assertTrue(String.format("%s is not final", field.getName()), Modifier.isFinal(field.getModifiers()));
+ @ParameterizedTest
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.ClassesOnly.class)
+ public void testPrefix(Class> headersClass) throws Exception {
+ Field field = getPrefixField(headersClass);
+ assertEquals(String.class, field.getType(), String.format("%s is not a String", field.getName()));
+ assertTrue(Modifier.isStatic(field.getModifiers()), String.format("%s is not static", field.getName()));
+ assertTrue(Modifier.isFinal(field.getModifiers()), String.format("%s is not final", field.getName()));
assertThat((String) field.get(null), startsWith(SolaceHeaders.PREFIX));
assertTrue(((String) field.get(null)).matches("[a-z][a-z_]+_"));
@@ -75,85 +58,94 @@ public void testPrefix() throws Exception {
}
}
- @Test
- public void testFieldDeclaration() {
- for (Field field : getAllHeaderFields()) {
- assertEquals(String.format("%s is not a String", field.getName()), String.class, field.getType());
- assertTrue(String.format("%s is not final", field.getName()), Modifier.isFinal(field.getModifiers()));
+ @ParameterizedTest
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.ClassesOnly.class)
+ public void testFieldDeclaration(Class> headersClass) {
+ for (Field field : getAllHeaderFields(headersClass)) {
+ assertEquals(String.class, field.getType(), String.format("%s is not a String", field.getName()));
+ assertTrue(Modifier.isFinal(field.getModifiers()), String.format("%s is not final", field.getName()));
}
}
- @Test
- public void testFieldNameSyntax() throws Exception {
- for (Field field : getAllHeaderFields()) {
- assertTrue(String.format("%s name does not have proper syntax", field.getName()),
- field.getName().matches("[A-Z][A-Z_]+[A-Z]"));
+ @ParameterizedTest
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.ClassesOnly.class)
+ public void testFieldNameSyntax(Class> headersClass) throws Exception {
+ for (Field field : getAllHeaderFields(headersClass)) {
+ assertTrue(field.getName().matches("[A-Z][A-Z_]+[A-Z]"),
+ String.format("%s name does not have proper syntax", field.getName()));
assertThat(String.format("%s name should not start with prefix", field.getName()),
- field.getName(), not(startsWithIgnoringCase((String) getPrefixField().get(null))));
+ field.getName(), not(startsWithIgnoringCase((String) getPrefixField(headersClass).get(null))));
String noPrefixHeader = ((String) field.get(null))
- .substring(((String) getPrefixField().get(null)).length());
- assertEquals(String.format(
+ .substring(((String) getPrefixField(headersClass).get(null)).length());
+ assertEquals(camelCaseToSnakeCase(noPrefixHeader).toUpperCase(), field.getName(), String.format(
"%s name should be the prefix-trimmed, fully-capitalized, '_'-delimited version of %s",
- field.getName(), field.get(null)),
- camelCaseToSnakeCase(noPrefixHeader).toUpperCase(), field.getName());
+ field.getName(), field.get(null)));
}
}
- @Test
- public void testHeaderSyntax() throws Exception {
- for (String header : getAllHeaders()) {
- String prefix = (String) getPrefixField().get(null);
+ @ParameterizedTest
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.ClassesOnly.class)
+ public void testHeaderSyntax(Class> headersClass) throws Exception {
+ for (String header : getAllHeaders(headersClass)) {
+ String prefix = (String) getPrefixField(headersClass).get(null);
assertThat(header, startsWith(prefix));
- assertTrue(String.format("%s does not have proper syntax", header),
- header.matches(prefix + "[a-z][a-zA-Z]+"));
+ assertTrue(header.matches(prefix + "[a-z][a-zA-Z]+"),
+ String.format("%s does not have proper syntax", header));
}
}
- @Test
- public void testUniqueHeaders() {
- List headers = getAllHeaders();
- assertEquals(String.join(", ", headers) + " does not have unique values",
- headers.stream().distinct().count(), headers.size());
+ @ParameterizedTest
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.ClassesOnly.class)
+ public void testUniqueHeaders(Class> headersClass) {
+ List headers = getAllHeaders(headersClass);
+ assertEquals(headers.stream().distinct().count(), headers.size(),
+ String.join(", ", headers) + " does not have unique values");
}
- @Test
- public void testHeadersHaveMetaObjects() {
- List headers = getAllHeaders();
+ @ParameterizedTest(name = "[{index}] {0}")
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.class)
+ public void testHeadersHaveMetaObjects(Class> headersClass, Map> headersMeta) {
+ List headers = getAllHeaders(headersClass);
assertEquals(headersMeta.size(), headers.size());
for (String header : headers) {
assertThat(headersMeta.keySet(), hasItem(header));
}
}
- @Test
- public void testValidMeta() {
+ @ParameterizedTest
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.MetaOnly.class)
+ public void testValidMeta(Map> headersMeta) {
headersMeta.values()
.forEach(m -> {
assertNotNull(m.getType());
- assertFalse(String.format("primitives not supported by %s", MessageHeaders.class.getSimpleName()),
- m.getType().isPrimitive());
+ assertFalse(m.getType().isPrimitive(),
+ String.format("primitives not supported by %s", MessageHeaders.class.getSimpleName()));
});
}
- @Test
- public void testUniqueMetaNames() {
- assertEquals(String.join(", ", headersMeta.keySet()) + " does not have unique values",
- headersMeta.keySet().size(), headersMeta.keySet().stream().distinct().count());
+ @ParameterizedTest
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.MetaOnly.class)
+ public void testUniqueMetaNames(Map> headersMeta) {
+ assertEquals(headersMeta.keySet().size(), headersMeta.keySet().stream().distinct().count(),
+ String.join(", ", headersMeta.keySet()) + " does not have unique values");
}
- @Test
- public void testMetaReadActions() {
+ @ParameterizedTest(name = "[{index}] {0}")
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.class)
+ public void testMetaReadActions(Class> headersClass, Map> headersMeta) {
if (!(headersClass.equals(SolaceHeaders.class))) {
logger.info(String.format("Test does not apply to %s", headersClass.getSimpleName()));
return;
}
- XMLMessage xmlMessage = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
+ XMLMessage xmlMessage = Mockito.spy(JCSMPFactory.onlyInstance().createMessage(TextMessage.class));
@SuppressWarnings("unchecked") Map> solaceHeaderMeta =
(Map>) headersMeta;
+ Mockito.doReturn(100).when(xmlMessage).getDeliveryCount();
+
for (Map.Entry> headerMeta : solaceHeaderMeta.entrySet()) {
if (!headerMeta.getValue().isReadable()) continue;
assertThat(headerMeta.getKey(), headerMeta.getValue().getReadAction().apply(xmlMessage),
@@ -161,8 +153,10 @@ public void testMetaReadActions() {
}
}
- @Test
- public void testMetaWriteActions() throws Exception {
+ @ParameterizedTest(name = "[{index}] {0}")
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.class)
+ public void testMetaWriteActions(Class> headersClass, Map> headersMeta)
+ throws Exception {
if (!(headersClass.equals(SolaceHeaders.class))) {
logger.info(String.format("Test does not apply to %s", headersClass.getSimpleName()));
return;
@@ -179,7 +173,7 @@ public void testMetaWriteActions() throws Exception {
Object value;
try {
if (Number.class.isAssignableFrom(type)) {
- value = type.getConstructor(String.class).newInstance("" + RandomUtils.nextInt(100));
+ value = type.getConstructor(String.class).newInstance("" + RandomUtils.nextInt(0, 100));
} else if (Boolean.class.isAssignableFrom(type)) {
value = true;
} else if (String.class.isAssignableFrom(type)) {
@@ -207,11 +201,12 @@ public void testMetaWriteActions() throws Exception {
}
logger.info("Message Dump:\n" + xmlMessage.dump(XMLMessage.MSGDUMP_FULL));
- logger.info("Message String:\n" + xmlMessage.toString());
+ logger.info("Message String:\n" + xmlMessage);
}
- @Test
- public void testDefaultValueOverride() {
+ @ParameterizedTest(name = "[{index}] {0}")
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.class)
+ public void testDefaultValueOverride(Class> headersClass, Map> headersMeta) {
if (!(headersClass.equals(SolaceHeaders.class))) {
logger.info(String.format("Test does not apply to %s", headersClass.getSimpleName()));
return;
@@ -231,8 +226,8 @@ public void testDefaultValueOverride() {
Object value = headerMeta.getValue().getDefaultValueOverride();
if (headerMeta.getValue().isReadable()) {
- assertNotEquals("Overridden default value is the same as the original default value",
- headerMeta.getValue().getReadAction().apply(xmlMessage), value);
+ assertNotEquals(headerMeta.getValue().getReadAction().apply(xmlMessage), value,
+ "Overridden default value is the same as the original default value");
}
@@ -248,11 +243,13 @@ public void testDefaultValueOverride() {
}
logger.info("Message Dump:\n" + xmlMessage.dump(XMLMessage.MSGDUMP_FULL));
- logger.info("Message String:\n" + xmlMessage.toString());
+ logger.info("Message String:\n" + xmlMessage);
}
- @Test
- public void testFailMetaWriteActionsWithInvalidType() {
+ @ParameterizedTest(name = "[{index}] {0}")
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.class)
+ public void testFailMetaWriteActionsWithInvalidType(Class> headersClass,
+ Map> headersMeta) {
if (!(headersClass.equals(SolaceHeaders.class))) {
logger.info(String.format("Test does not apply to %s", headersClass.getSimpleName()));
return;
@@ -274,9 +271,10 @@ public void testFailMetaWriteActionsWithInvalidType() {
}
}
- @Test
- public void testNameJmsCompatibility() {
- for (String headerName : getAllHeaders()) {
+ @ParameterizedTest(name = "[{index}] {0}")
+ @ArgumentsSource(SolaceSpringHeaderArgumentsProvider.class)
+ public void testNameJmsCompatibility(Class> headersClass, Map> headersMeta) {
+ for (String headerName : getAllHeaders(headersClass)) {
assertTrue(Character.isJavaIdentifierStart(headerName.charAt(0)));
for (int i = 1; i < headerName.length(); i++) {
assertTrue(Character.isJavaIdentifierPart(headerName.charAt(i)));
@@ -313,19 +311,19 @@ public void testNameJmsCompatibility() {
}
}
- private Field getPrefixField() throws NoSuchFieldException {
+ private Field getPrefixField(Class> headersClass) throws NoSuchFieldException {
return headersClass.getDeclaredField("PREFIX");
}
- private List getAllHeaderFields() {
+ private List getAllHeaderFields(Class> headersClass) {
return Arrays.stream(headersClass.getDeclaredFields())
.filter(f -> Modifier.isPublic(f.getModifiers()))
.filter(f -> Modifier.isStatic(f.getModifiers()))
.collect(Collectors.toList());
}
- private List getAllHeaders() {
- return getAllHeaderFields().stream().map(f -> {
+ private List getAllHeaders(Class> headersClass) {
+ return getAllHeaderFields(headersClass).stream().map(f -> {
try {
return (String) f.get(null);
} catch (IllegalAccessException e) {
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/outbound/JCSMPOutboundMessageHandlerTest.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/outbound/JCSMPOutboundMessageHandlerTest.java
index 40bc694a..a278b0e1 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/outbound/JCSMPOutboundMessageHandlerTest.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/outbound/JCSMPOutboundMessageHandlerTest.java
@@ -16,10 +16,9 @@
import com.solacesystems.jcsmp.JCSMPStreamingPublishCorrelatingEventHandler;
import com.solacesystems.jcsmp.XMLMessage;
import com.solacesystems.jcsmp.XMLMessageProducer;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.Timeout;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@@ -29,21 +28,20 @@
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.MessageBuilder;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+@Timeout(value = 10)
public class JCSMPOutboundMessageHandlerTest {
- @Rule
- public Timeout globalTimeout = new Timeout(10, TimeUnit.SECONDS);
-
private JCSMPOutboundMessageHandler messageHandler;
private JCSMPStreamingPublishCorrelatingEventHandler pubEventHandler;
private ArgumentCaptor xmlMessageCaptor;
private final MessageChannel errChannel = Mockito.mock(MessageChannel.class);
- @Before
+ @BeforeEach
public void init() throws JCSMPException {
JCSMPSession session = Mockito.mock(JCSMPSession.class);
XMLMessageProducer messageProducer = Mockito.mock(XMLMessageProducer.class);
@@ -93,14 +91,14 @@ public void test_handleError_withInTimeout() {
pubEventHandler.handleErrorEx(createCorrelationKey(correlationData, msg), new JCSMPException("ooooops"), 1111);
ExecutionException exception = assertThrows(ExecutionException.class, () -> correlationData.getFuture().get(100, TimeUnit.MILLISECONDS));
- assertTrue(exception instanceof ExecutionException);
+ assertNotNull(exception);
assertTrue(exception.getCause() instanceof MessagingException);
assertTrue(exception.getCause().getCause() instanceof JCSMPException);
assertEquals("ooooops", exception.getCause().getCause().getMessage());
}
@Test()
- public void test_responseReceived_withOutTimeout() throws ExecutionException, InterruptedException, TimeoutException {
+ public void test_responseReceived_withOutTimeout() {
CorrelationData correlationData = new CorrelationData();
messageHandler.handleMessage(getMessage(correlationData));
@@ -151,7 +149,7 @@ private ErrorChannelSendingCorrelationKey getCorrelationKey() {
return (ErrorChannelSendingCorrelationKey) xmlMessageCaptor.getValue().getCorrelationKey();
}
- private ErrorChannelSendingCorrelationKey createCorrelationKey(CorrelationData correlationData, Message msg) {
+ private ErrorChannelSendingCorrelationKey createCorrelationKey(CorrelationData correlationData, Message> msg) {
ErrorChannelSendingCorrelationKey key = new ErrorChannelSendingCorrelationKey(
msg,
Mockito.mock(MessageChannel.class),
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/properties/SolaceConsumerPropertiesTest.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/properties/SolaceConsumerPropertiesTest.java
new file mode 100644
index 00000000..5bea9d23
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/properties/SolaceConsumerPropertiesTest.java
@@ -0,0 +1,39 @@
+package com.solace.spring.cloud.stream.binder.properties;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class SolaceConsumerPropertiesTest {
+ @ParameterizedTest
+ @ValueSource(ints = {1, 256})
+ public void testSetBatchMaxSize(int batchMaxSize) {
+ SolaceConsumerProperties consumerProperties = new SolaceConsumerProperties();
+ consumerProperties.setBatchMaxSize(batchMaxSize);
+ assertEquals(batchMaxSize, consumerProperties.getBatchMaxSize());
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 0})
+ public void testFailSetBatchMaxSize(int batchMaxSize) {
+ assertThrows(IllegalArgumentException.class, () -> new SolaceConsumerProperties()
+ .setBatchMaxSize(batchMaxSize));
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {0, 1, 1000})
+ public void testSetBatchTimeout(int batchTimeout) {
+ SolaceConsumerProperties consumerProperties = new SolaceConsumerProperties();
+ consumerProperties.setBatchTimeout(batchTimeout);
+ assertEquals(batchTimeout, consumerProperties.getBatchTimeout());
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {-1})
+ public void testFailSetBatchTimeout(int batchTimeout) {
+ assertThrows(IllegalArgumentException.class, () -> new SolaceConsumerProperties()
+ .setBatchTimeout(batchTimeout));
+ }
+}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/provisioning/SolaceProvisioningUtilQueueNameTest.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/provisioning/SolaceProvisioningUtilQueueNameTest.java
index 9bfd5ae3..64c2caea 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/provisioning/SolaceProvisioningUtilQueueNameTest.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/provisioning/SolaceProvisioningUtilQueueNameTest.java
@@ -1,35 +1,30 @@
package com.solace.spring.cloud.stream.binder.provisioning;
import com.solace.spring.cloud.stream.binder.properties.SolaceConsumerProperties;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
+import com.solace.spring.cloud.stream.binder.properties.SolaceProducerProperties;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
+import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
+import org.springframework.cloud.stream.provisioning.ProvisioningException;
+
+import java.util.*;
+import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.matchesRegex;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
-@RunWith(Parameterized.class)
public class SolaceProvisioningUtilQueueNameTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SolaceProvisioningUtilQueueNameTest.class);
- private final String destination;
- private final String groupName;
- private final boolean isAnonymous;
- private final SolaceConsumerProperties consumerProperties;
-
- private static final Log logger = LogFactory.getLog(SolaceProvisioningUtilQueueNameTest.class);
-
- @Parameters(name = "dest: {0}, group: {1}, prefix: {2}, useGroup: {3}, useGroupInEQ: {4}, useFamiliarity: {5}, useDestEnc: {6}")
- public static Collection data() {
+ public static Stream arguments() {
List> testCases = new ArrayList<>();
// destination
@@ -84,7 +79,7 @@ public static Collection data() {
testCases.addAll(dupeList);
}
- return testCases.stream().map(List::toArray).collect(Collectors.toList());
+ return testCases.stream().map(List::toArray).map(Arguments::of);
}
private static List> deepClone(List> input) {
@@ -95,30 +90,18 @@ private static List> deepClone(List> input) {
return cloned;
}
- public SolaceProvisioningUtilQueueNameTest(String destination, String groupName, String prefix, boolean useGroupName,
- boolean useGroupNameInErrorQueue, boolean useFamiliarity,
- boolean useDestinationEncoding) {
- this.destination = destination;
- this.groupName = groupName;
- this.isAnonymous = groupName == null;
-
- this.consumerProperties = new SolaceConsumerProperties();
- if (prefix != null) {
- this.consumerProperties.setQueueNamePrefix(prefix);
- } else {
- assertEquals("scst", this.consumerProperties.getQueueNamePrefix());
- }
- this.consumerProperties.setUseGroupNameInQueueName(useGroupName);
- this.consumerProperties.setUseGroupNameInErrorQueueName(useGroupNameInErrorQueue);
- this.consumerProperties.setUseFamiliarityInQueueName(useFamiliarity);
- this.consumerProperties.setUseDestinationEncodingInQueueName(useDestinationEncoding);
- }
+ @ParameterizedTest(name = "[{index}] dest={0} group={1} prefix={2} useGroup={3} useGroupInEQ={4} useFamiliarity={5} useDestEnc={6}")
+ @MethodSource("arguments")
+ public void getQueueName(String destination, String groupName, String prefix, boolean useGroupName,
+ boolean useGroupNameInErrorQueue, boolean useFamiliarity,
+ boolean useDestinationEncoding) {
+ SolaceConsumerProperties consumerProperties = createConsumerProperties(prefix, useGroupName,
+ useGroupNameInErrorQueue, useFamiliarity, useDestinationEncoding);
+ boolean isAnonymous = groupName == null;
- @Test
- public void getQueueName() {
- String actual = SolaceProvisioningUtil.getQueueNames(destination, groupName, consumerProperties, isAnonymous)
+ String actual = SolaceProvisioningUtil.getQueueNames(destination, groupName, new ExtendedConsumerProperties<>(consumerProperties), isAnonymous)
.getConsumerGroupQueueName();
- logger.info("Testing Queue Name: " + actual);
+ LOGGER.info("Testing Queue Name: {}", actual);
int levelIdx = 0;
String[] levels = actual.split("/");
@@ -129,7 +112,7 @@ public void getQueueName() {
}
if (consumerProperties.isUseFamiliarityInQueueName()) {
- assertEquals(actual, isAnonymous ? "an" : "wk", levels[levelIdx]);
+ assertEquals(isAnonymous ? "an" : "wk", levels[levelIdx]);
levelIdx++;
}
@@ -161,12 +144,19 @@ public void getQueueName() {
}
}
- @Test
- public void getErrorQueueName() {
- String actual = SolaceProvisioningUtil.getQueueNames(destination, groupName, consumerProperties, isAnonymous)
+ @ParameterizedTest(name = "[{index}] dest={0} group={1} prefix={2} useGroup={3} useGroupInEQ={4} useFamiliarity={5} useDestEnc={6}")
+ @MethodSource("arguments")
+ public void getErrorQueueName(String destination, String groupName, String prefix, boolean useGroupName,
+ boolean useGroupNameInErrorQueue, boolean useFamiliarity,
+ boolean useDestinationEncoding) {
+ SolaceConsumerProperties consumerProperties = createConsumerProperties(prefix, useGroupName,
+ useGroupNameInErrorQueue, useFamiliarity, useDestinationEncoding);
+ boolean isAnonymous = groupName == null;
+
+ String actual = SolaceProvisioningUtil.getQueueNames(destination, groupName, new ExtendedConsumerProperties<>(consumerProperties), isAnonymous)
.getErrorQueueName();
- logger.info("Testing Error Queue Name: " + actual);
+ LOGGER.info("Testing Error Queue Name: {}", actual);
int levelIdx = 0;
String[] levels = actual.split("/");
@@ -180,7 +170,7 @@ public void getErrorQueueName() {
levelIdx++;
if (consumerProperties.isUseFamiliarityInQueueName()) {
- assertEquals(actual, isAnonymous ? "an" : "wk", levels[levelIdx]);
+ assertEquals(isAnonymous ? "an" : "wk", levels[levelIdx]);
levelIdx++;
}
@@ -211,4 +201,171 @@ public void getErrorQueueName() {
levelIdx++;
}
}
+
+ private SolaceConsumerProperties createConsumerProperties(String prefix, boolean useGroupName,
+ boolean useGroupNameInErrorQueue, boolean useFamiliarity,
+ boolean useDestinationEncoding) {
+ SolaceConsumerProperties consumerProperties = new SolaceConsumerProperties();
+ if (prefix != null) {
+ consumerProperties.setQueueNamePrefix(prefix);
+ } else {
+ assertEquals("scst", consumerProperties.getQueueNamePrefix());
+ }
+ consumerProperties.setUseGroupNameInQueueName(useGroupName);
+ consumerProperties.setUseGroupNameInErrorQueueName(useGroupNameInErrorQueue);
+ consumerProperties.setUseFamiliarityInQueueName(useFamiliarity);
+ consumerProperties.setUseDestinationEncodingInQueueName(useDestinationEncoding);
+ return consumerProperties;
+ }
+
+ @Test
+ public void testDefaultQueueNameExpressionsWithPrefixAndGroupAndDestinationContainingWhitespaces() {
+ ExtendedConsumerProperties consumerProperties = new ExtendedConsumerProperties<>(new SolaceConsumerProperties());
+ consumerProperties.getExtension().setQueueNamePrefix(" aQueueNamePrefixWithSpaces ");
+
+ String destination = " destination/with/spaces ";
+ String group = " aGroupWithSpaces ";
+
+ SolaceProvisioningUtil.QueueNames queueNames = SolaceProvisioningUtil.getQueueNames(destination, group, consumerProperties,
+ SolaceProvisioningUtil.isAnonQueue(group));
+
+ assertEquals("aQueueNamePrefixWithSpaces/wk/aGroupWithSpaces/plain/destination/with/spaces", queueNames.getConsumerGroupQueueName());
+ assertEquals("aQueueNamePrefixWithSpaces/error/wk/aGroupWithSpaces/plain/destination/with/spaces", queueNames.getErrorQueueName());
+ }
+
+ @Test
+ public void testDefaultQueueNameExpressionsWithGroupAsWhiteSpacesOnlyGeneratesAGroup() {
+ ExtendedConsumerProperties consumerProperties = new ExtendedConsumerProperties<>(new SolaceConsumerProperties());
+
+ String destination = "simple/destination";
+ String group = " ";
+
+ SolaceProvisioningUtil.QueueNames queueNames = SolaceProvisioningUtil
+ .getQueueNames(destination, group, consumerProperties, SolaceProvisioningUtil.isAnonQueue(group));
+
+ assertThat(queueNames.getConsumerGroupQueueName(), matchesRegex("scst\\/an\\/\\b[0-9a-f]{8}\\b(?:-[0-9a-f]{4}){3}-\\b[0-9a-f]{12}\\b\\/plain\\/simple\\/destination"));
+ assertThat(queueNames.getErrorQueueName(), matchesRegex("scst\\/error\\/an\\/\\b[0-9a-f]{8}\\b(?:-[0-9a-f]{4}){3}-\\b[0-9a-f]{12}\\b\\/plain\\/simple\\/destination"));
+ }
+
+ @Test
+ public void testDefaultQueueNameExpressionsWithGroupAsNullGeneratesAGroup() {
+ ExtendedConsumerProperties consumerProperties = new ExtendedConsumerProperties<>(new SolaceConsumerProperties());
+
+ String destination = "simple/destination";
+ String group = null;
+
+ SolaceProvisioningUtil.QueueNames queueNames = SolaceProvisioningUtil
+ .getQueueNames(destination, group, consumerProperties, SolaceProvisioningUtil.isAnonQueue(group));
+
+ assertThat(queueNames.getConsumerGroupQueueName(), matchesRegex("scst\\/an\\/\\b[0-9a-f]{8}\\b(?:-[0-9a-f]{4}){3}-\\b[0-9a-f]{12}\\b\\/plain\\/simple\\/destination"));
+ assertThat(queueNames.getErrorQueueName(), matchesRegex("scst\\/error\\/an\\/\\b[0-9a-f]{8}\\b(?:-[0-9a-f]{4}){3}-\\b[0-9a-f]{12}\\b\\/plain\\/simple\\/destination"));
+ }
+
+ @Test
+ public void testQueueNameExpressionWithStaticValue() {
+ SolaceConsumerProperties consumerProperties = createConsumerProperties(null, true, true, true, true);
+ //The escaped single quote '' resolves to a single quote in the actual queue name
+ consumerProperties.setQueueNameExpression("'My/Static.QueueName-_''>*!@#$%^&()+=#{test}:[]{}|\\\"-~'");
+
+ String actual = SolaceProvisioningUtil
+ .getQueueNames("unused/destination", "unusedGroup", new ExtendedConsumerProperties<>(consumerProperties), true)
+ .getConsumerGroupQueueName();
+ assertEquals("My/Static.QueueName-_'>*!@#$%^&()+=#{test}:[]{}|\\\"-~", actual);
+ }
+
+ @Test
+ public void testQueueNameExpressionWithSolaceProperties() {
+ SolaceConsumerProperties consumerProperties = createConsumerProperties("myCustomPrefix", true, true, true, true);
+ consumerProperties.setQueueMaxMsgRedelivery(5);
+
+ consumerProperties.setQueueNameExpression("properties.solace.queueNamePrefix + '_' + properties.solace.useGroupNameInQueueName + '_' + properties.solace.queueMaxMsgRedelivery + '_' + properties.solace.errorQueueNameOverride");
+
+ String actual = SolaceProvisioningUtil
+ .getQueueNames("unused/destination", "unusedGroup", new ExtendedConsumerProperties<>(consumerProperties), false)
+ .getConsumerGroupQueueName();
+ assertEquals("myCustomPrefix_true_5_null", actual);
+ }
+
+ @Test
+ public void testQueueNameExpressionWithLongFormSolaceProperties() {
+ SolaceConsumerProperties consumerProperties = createConsumerProperties("myCustomPrefix", true, true, true, true);
+ consumerProperties.setQueueMaxMsgRedelivery(5);
+
+ consumerProperties.setQueueNameExpression("properties.spring.extension.queueNamePrefix + '_' + properties.spring.extension.useGroupNameInQueueName + '_' + properties.spring.extension.queueMaxMsgRedelivery + '_' + properties.spring.extension.errorQueueNameOverride");
+
+ String actual = SolaceProvisioningUtil
+ .getQueueNames("unused/destination", "unusedGroup", new ExtendedConsumerProperties<>(consumerProperties), false)
+ .getConsumerGroupQueueName();
+ assertEquals("myCustomPrefix_true_5_null", actual);
+ }
+
+ @Test
+ public void testQueueNameExpressionWithSpringProperties() {
+ ExtendedConsumerProperties consumerProperties = new ExtendedConsumerProperties<>(new SolaceConsumerProperties());
+ consumerProperties.setMaxAttempts(22);
+ consumerProperties.setAutoStartup(true);
+ consumerProperties.setDefaultRetryable(false);
+ consumerProperties.getExtension().setQueueNameExpression("properties.spring.maxAttempts + '_' + properties.spring.autoStartup + '_' + properties.spring.defaultRetryable");
+
+ String actual = SolaceProvisioningUtil
+ .getQueueNames("simple/destination", "groupName", consumerProperties, false)
+ .getConsumerGroupQueueName();
+ assertEquals("22_true_false", actual);
+ }
+
+ @Test
+ public void testErrorQueueNameExpressionWithSolaceAndSpringProperties() {
+ ExtendedConsumerProperties consumerProperties = new ExtendedConsumerProperties<>(new SolaceConsumerProperties());
+ consumerProperties.setMaxAttempts(10);
+ consumerProperties.getExtension().setQueueMaxMsgRedelivery(11);
+ consumerProperties.getExtension().setErrorQueueNameExpression("properties.spring.maxAttempts + '_' + properties.solace.queueMaxMsgRedelivery + '_' + properties.spring.extension.errorQueueNameOverride");
+
+ String actual = SolaceProvisioningUtil
+ .getQueueNames("unused/destination", "unusedGroup", consumerProperties, false)
+ .getErrorQueueName();
+ assertEquals("10_11_null", actual);
+ }
+
+ @Test
+ public void testErrorQueueNameOverrideTakesPrecedenceOverErrorQueueNameExpression() {
+ ExtendedConsumerProperties consumerProperties = new ExtendedConsumerProperties<>(new SolaceConsumerProperties());
+ consumerProperties.getExtension().setErrorQueueNameOverride("ErrorQueueNameOverride");
+ consumerProperties.getExtension().setErrorQueueNameExpression("'AnErrorQueueNameExpression'");
+
+ String actual = SolaceProvisioningUtil
+ .getQueueNames("unused/destination", "unusedGroup", consumerProperties, false)
+ .getErrorQueueName();
+ assertEquals("ErrorQueueNameOverride", actual);
+ }
+
+ @Test
+ public void testInvalidQueueNameExpression() {
+ String invalidExpression = "This is an invalid expression";
+ ExtendedConsumerProperties consumerProperties = new ExtendedConsumerProperties<>(new SolaceConsumerProperties());
+ consumerProperties.getExtension().setQueueNameExpression(invalidExpression);
+ try {
+ SolaceProvisioningUtil.getQueueNames("unused/destination", "unusedGroup", consumerProperties, false);
+ fail("Expected expression evaluation to fail");
+ } catch (Exception e) {
+ Assertions.assertThat(e).isInstanceOf(ProvisioningException.class);
+ Assertions.assertThat(e.getMessage()).contains("Failed to evaluate Spring expression: " + invalidExpression);
+ }
+ }
+
+ @Test
+ public void testQueueNameExpressionsForRequiredGroups() {
+ String group1 = "group1_hasOverride";
+ String group2 = "group2_noOverride";
+
+ ExtendedProducerProperties producerProperties = new ExtendedProducerProperties<>(new SolaceProducerProperties());
+ producerProperties.setRequiredGroups(group1, group2);
+ producerProperties.getExtension().setQueueNameExpression("'DefaultQueueNameExpression'");
+
+ Map queueNameExpressionsForRequiredGroups = new HashMap<>();
+ queueNameExpressionsForRequiredGroups.put(group1, "'ExpressionOverrideForGroup1'");
+ producerProperties.getExtension().setQueueNameExpressionsForRequiredGroups(queueNameExpressionsForRequiredGroups);
+
+ assertEquals("ExpressionOverrideForGroup1", SolaceProvisioningUtil.getQueueName("unused/destination", group1, producerProperties));
+ assertEquals("DefaultQueueNameExpression", SolaceProvisioningUtil.getQueueName("unused/destination", group2, producerProperties));
+ }
}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/provisioning/SolaceProvisioningUtilTest.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/provisioning/SolaceProvisioningUtilTest.java
index e6715f39..4165fadc 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/provisioning/SolaceProvisioningUtilTest.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/provisioning/SolaceProvisioningUtilTest.java
@@ -2,26 +2,27 @@
import com.solace.spring.cloud.stream.binder.properties.SolaceConsumerProperties;
import com.solace.spring.cloud.stream.binder.provisioning.SolaceProvisioningUtil.QueueNames;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
public class SolaceProvisioningUtilTest {
@Test
public void testConsumerErrorQueueNameOverride() {
- SolaceConsumerProperties consumerProperties = new SolaceConsumerProperties();
- consumerProperties.setErrorQueueNameOverride("some-custom-named-error-queue-name");
+ ExtendedConsumerProperties consumerProperties = new ExtendedConsumerProperties<>(new SolaceConsumerProperties());
+ consumerProperties.getExtension().setErrorQueueNameOverride("some-custom-named-error-queue-name");
QueueNames queueNames = SolaceProvisioningUtil.getQueueNames("topic", "group",
consumerProperties, false);
- assertEquals(consumerProperties.getErrorQueueNameOverride(), queueNames.getErrorQueueName());
+ assertEquals(consumerProperties.getExtension().getErrorQueueNameOverride(), queueNames.getErrorQueueName());
}
@Test
public void testAnonConsumerErrorQueueNameOverride() {
- SolaceConsumerProperties consumerProperties = new SolaceConsumerProperties();
- consumerProperties.setErrorQueueNameOverride("some-custom-named-error-queue-name");
+ ExtendedConsumerProperties consumerProperties = new ExtendedConsumerProperties<>(new SolaceConsumerProperties());
+ consumerProperties.getExtension().setErrorQueueNameOverride("some-custom-named-error-queue-name");
QueueNames queueNames = SolaceProvisioningUtil.getQueueNames("topic", null,
consumerProperties, true);
- assertEquals(consumerProperties.getErrorQueueNameOverride(), queueNames.getErrorQueueName());
+ assertEquals(consumerProperties.getExtension().getErrorQueueNameOverride(), queueNames.getErrorQueueName());
}
}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/junit/extension/pubsubplus/provider/PubSubPlusSpringProvider.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/junit/extension/pubsubplus/provider/PubSubPlusSpringProvider.java
new file mode 100644
index 00000000..a24c489c
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/junit/extension/pubsubplus/provider/PubSubPlusSpringProvider.java
@@ -0,0 +1,80 @@
+package com.solace.spring.cloud.stream.binder.test.junit.extension.pubsubplus.provider;
+
+import com.solace.test.integration.junit.jupiter.extension.PubSubPlusExtension;
+import com.solace.test.integration.semp.v2.SempV2Api;
+import com.solacesystems.jcsmp.JCSMPProperties;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class PubSubPlusSpringProvider implements PubSubPlusExtension.ExternalProvider {
+ private static final Logger LOGGER = LoggerFactory.getLogger(PubSubPlusSpringProvider.class);
+
+ @Override
+ public boolean isValid(ExtensionContext extensionContext) {
+ ApplicationContext applicationContext = SpringExtension.getApplicationContext(extensionContext);
+ Environment contextEnvironment = applicationContext.getEnvironment();
+ try {
+ applicationContext.getBean(JCSMPProperties.class);
+ } catch (NoSuchBeanDefinitionException e) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("Didn't find required bean: " + JCSMPProperties.class, e);
+ }
+ return false;
+ }
+
+ if (!Stream.of(TestProperties.values()).map(TestProperties::getProperty)
+ .allMatch(contextEnvironment::containsProperty)) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("Didn't find required test properties: [{}]", Stream.of(TestProperties.values())
+ .map(TestProperties::getProperty)
+ .collect(Collectors.joining(", ")));
+ }
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void init(ExtensionContext extensionContext) {
+ // Do not cache anything in the root store.
+ // There are tests which modify the config at runtime and caching will conflict with those tests.
+ }
+
+ @Override
+ public JCSMPProperties createJCSMPProperties(ExtensionContext extensionContext) {
+ return SpringExtension.getApplicationContext(extensionContext).getBean(JCSMPProperties.class);
+ }
+
+ @Override
+ public SempV2Api createSempV2Api(ExtensionContext extensionContext) {
+ Environment contextEnvironment = SpringExtension.getApplicationContext(extensionContext).getEnvironment();
+ String mgmtHost = contextEnvironment.getRequiredProperty(TestProperties.MGMT_HOST.getProperty());
+ String mgmtUsername = contextEnvironment.getRequiredProperty(TestProperties.MGMT_USERNAME.getProperty());
+ String mgmtPassword = contextEnvironment.getRequiredProperty(TestProperties.MGMT_PASSWORD.getProperty());
+ return new SempV2Api(mgmtHost, mgmtUsername, mgmtPassword);
+ }
+
+ private enum TestProperties {
+ MGMT_HOST("test.solace.mgmt.host"),
+ MGMT_USERNAME("test.solace.mgmt.username"),
+ MGMT_PASSWORD("test.solace.mgmt.password");
+
+ private final String property;
+
+ TestProperties(String propertyName) {
+ this.property = propertyName;
+ }
+
+ public String getProperty() {
+ return property;
+ }
+ }
+}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/junit/param/provider/SolaceSpringHeaderArgumentsProvider.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/junit/param/provider/SolaceSpringHeaderArgumentsProvider.java
new file mode 100644
index 00000000..da361649
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/junit/param/provider/SolaceSpringHeaderArgumentsProvider.java
@@ -0,0 +1,43 @@
+package com.solace.spring.cloud.stream.binder.test.junit.param.provider;
+
+import com.solace.spring.cloud.stream.binder.messaging.HeaderMeta;
+import com.solace.spring.cloud.stream.binder.messaging.SolaceBinderHeaderMeta;
+import com.solace.spring.cloud.stream.binder.messaging.SolaceBinderHeaders;
+import com.solace.spring.cloud.stream.binder.messaging.SolaceHeaderMeta;
+import com.solace.spring.cloud.stream.binder.messaging.SolaceHeaders;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
+public class SolaceSpringHeaderArgumentsProvider implements ArgumentsProvider {
+ private static final Map, Map>> ARGS;
+
+ static {
+ ARGS = new HashMap<>();
+ ARGS.put(SolaceHeaders.class, SolaceHeaderMeta.META);
+ ARGS.put(SolaceBinderHeaders.class, SolaceBinderHeaderMeta.META);
+ }
+
+ @Override
+ public Stream extends Arguments> provideArguments(ExtensionContext context) {
+ return ARGS.entrySet().stream().map(e -> Arguments.of(e.getKey(), e.getValue()));
+ }
+
+ public static class ClassesOnly implements ArgumentsProvider {
+ @Override
+ public Stream extends Arguments> provideArguments(ExtensionContext context) {
+ return ARGS.keySet().stream().map(Arguments::of);
+ }
+ }
+
+ public static class MetaOnly implements ArgumentsProvider {
+ @Override
+ public Stream extends Arguments> provideArguments(ExtensionContext context) {
+ return ARGS.values().stream().map(Arguments::of);
+ }
+ }
+}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/util/RetryableAssertions.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/util/RetryableAssertions.java
new file mode 100644
index 00000000..c49be852
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/util/RetryableAssertions.java
@@ -0,0 +1,30 @@
+package com.solace.spring.cloud.stream.binder.test.util;
+
+import org.assertj.core.api.SoftAssertions;
+import org.assertj.core.api.SoftAssertionsProvider;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+public class RetryableAssertions {
+ public static Duration RETRY_INTERVAL = Duration.ofMillis(500);
+
+ public static void retryAssert(SoftAssertionsProvider.ThrowingRunnable assertRun) throws InterruptedException {
+ retryAssert(assertRun, 10, TimeUnit.SECONDS);
+ }
+
+ @SuppressWarnings("BusyWait")
+ public static void retryAssert(SoftAssertionsProvider.ThrowingRunnable assertRun, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ final long expiry = System.currentTimeMillis() + unit.toMillis(timeout);
+ SoftAssertions softAssertions;
+ do {
+ softAssertions = new SoftAssertions();
+ softAssertions.check(assertRun);
+ if (!softAssertions.wasSuccess()) {
+ Thread.sleep(RETRY_INTERVAL.toMillis());
+ }
+ } while (!softAssertions.wasSuccess() && System.currentTimeMillis() < expiry);
+ softAssertions.assertAll();
+ }
+}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/SerializableFoo.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/util/SerializableFoo.java
similarity index 67%
rename from solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/SerializableFoo.java
rename to solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/util/SerializableFoo.java
index d8711b46..78b45d23 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/SerializableFoo.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/util/SerializableFoo.java
@@ -1,13 +1,13 @@
-package com.solace.spring.cloud.stream.binder.util;
+package com.solace.spring.cloud.stream.binder.test.util;
import java.io.Serializable;
import java.util.Objects;
-class SerializableFoo implements Serializable {
- private String someVar0;
- private String someVar1;
+public class SerializableFoo implements Serializable {
+ private final String someVar0;
+ private final String someVar1;
- SerializableFoo(String someVar0, String someVar1) {
+ public SerializableFoo(String someVar0, String someVar1) {
this.someVar0 = someVar0;
this.someVar1 = someVar1;
}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/util/ThrowingFunction.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/util/ThrowingFunction.java
new file mode 100644
index 00000000..a94e6f03
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/test/util/ThrowingFunction.java
@@ -0,0 +1,19 @@
+package com.solace.spring.cloud.stream.binder.test.util;
+
+
+import java.util.function.Function;
+
+@FunctionalInterface
+public interface ThrowingFunction extends Function {
+
+ @Override
+ default R apply(T t) {
+ try {
+ return applyThrows(t);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ R applyThrows(T t) throws Throwable;
+}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorChannelSendingCorrelationKeyTest.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorChannelSendingCorrelationKeyTest.java
index ebe172dc..1a1153f1 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorChannelSendingCorrelationKeyTest.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorChannelSendingCorrelationKeyTest.java
@@ -3,7 +3,7 @@
import com.solacesystems.jcsmp.JCSMPFactory;
import com.solacesystems.jcsmp.TextMessage;
import org.assertj.core.api.SoftAssertions;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.StaticMessageHeaderAccessor;
import org.springframework.integration.channel.DirectChannel;
@@ -34,7 +34,7 @@ public void testNoErrorChannel() {
}
@Test
- public void testErrorChannel() {
+ public void testErrorChannel(SoftAssertions softly) {
Message> message = MessageBuilder.withPayload("test").build();
DirectChannel errorChannel = new DirectChannel();
ErrorChannelSendingCorrelationKey key = new ErrorChannelSendingCorrelationKey(message, errorChannel,
@@ -43,7 +43,6 @@ public void testErrorChannel() {
String description = "some failure";
Exception cause = new RuntimeException("test");
- SoftAssertions softly = new SoftAssertions();
errorChannel.subscribe(msg -> {
softly.assertThat(msg).isInstanceOf(ErrorMessage.class);
ErrorMessage errorMsg = (ErrorMessage) msg;
@@ -58,25 +57,22 @@ public void testErrorChannel() {
assertThat(exception).hasMessageStartingWith(description);
assertThat(exception).hasCause(cause);
assertThat(exception.getFailedMessage()).isEqualTo(message);
- softly.assertAll();
}
@SuppressWarnings("ThrowableNotThrown")
@Test
- public void testRawMessageHeader() {
+ public void testRawMessageHeader(SoftAssertions softly) {
Message> message = MessageBuilder.withPayload("test").build();
DirectChannel errorChannel = new DirectChannel();
ErrorChannelSendingCorrelationKey key = new ErrorChannelSendingCorrelationKey(message, errorChannel,
errorMessageStrategy);
key.setRawMessage(JCSMPFactory.onlyInstance().createMessage(TextMessage.class));
- SoftAssertions softly = new SoftAssertions();
errorChannel.subscribe(msg -> {
softly.assertThat(msg.getHeaders()).containsKey(IntegrationMessageHeaderAccessor.SOURCE_DATA);
softly.assertThat((Object) StaticMessageHeaderAccessor.getSourceData(msg)).isEqualTo(key.getRawMessage());
});
key.send("some failure", new RuntimeException("test"));
- softly.assertAll();
}
}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorQueueRepublishCorrelationKeyIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorQueueRepublishCorrelationKeyIT.java
index 7eb207d3..9c1bed62 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorQueueRepublishCorrelationKeyIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorQueueRepublishCorrelationKeyIT.java
@@ -1,8 +1,9 @@
package com.solace.spring.cloud.stream.binder.util;
import com.solace.spring.boot.autoconfigure.SolaceJavaAutoConfiguration;
-import com.solace.spring.cloud.stream.binder.ITBase;
import com.solace.spring.cloud.stream.binder.properties.SolaceConsumerProperties;
+import com.solace.test.integration.junit.jupiter.extension.PubSubPlusExtension;
+import com.solace.test.integration.semp.v2.SempV2Api;
import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnQueue;
import com.solacesystems.jcsmp.Consumer;
import com.solacesystems.jcsmp.EndpointProperties;
@@ -13,75 +14,47 @@
import com.solacesystems.jcsmp.Queue;
import com.solacesystems.jcsmp.TextMessage;
import com.solacesystems.jcsmp.XMLMessageProducer;
-import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
-import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer;
-import org.springframework.test.context.ContextConfiguration;
+import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import static com.solace.spring.cloud.stream.binder.test.util.RetryableAssertions.retryAssert;
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.assertThrows;
-import static org.junit.Assert.assertTrue;
-
-@RunWith(Parameterized.class)
-@ContextConfiguration(classes = SolaceJavaAutoConfiguration.class,
- initializers = ConfigFileApplicationContextInitializer.class)
-public class ErrorQueueRepublishCorrelationKeyIT extends ITBase {
- @Parameterized.Parameter
- public String parameterSetName; // Only used for parameter set naming
-
- @Parameterized.Parameter(1)
- public boolean isDurable;
-
- private String vpnName;
- private Queue queue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SpringJUnitConfig(classes = SolaceJavaAutoConfiguration.class,
+ initializers = ConfigDataApplicationContextInitializer.class)
+@ExtendWith(PubSubPlusExtension.class)
+public class ErrorQueueRepublishCorrelationKeyIT {
private Queue errorQueue;
private XMLMessageProducer producer;
- private FlowReceiverContainer flowReceiverContainer;
+ private final AtomicReference flowReceiverContainerReference = new AtomicReference<>();
private ErrorQueueInfrastructure errorQueueInfrastructure;
private RetryableTaskService retryableTaskService;
private static final Log logger = LogFactory.getLog(ErrorQueueRepublishCorrelationKeyIT.class);
- @Parameterized.Parameters(name = "{0}")
- public static Collection> headerSets() {
- return Arrays.asList(new Object[][]{
- {"Durable", true},
- {"Temporary", false}
- });
- }
-
- @Before
- public void setUp() throws Exception {
- vpnName = (String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME);
+ @BeforeEach
+ public void setUp(JCSMPSession jcsmpSession) throws Exception {
retryableTaskService = Mockito.spy(new RetryableTaskService());
- if (isDurable) {
- queue = JCSMPFactory.onlyInstance().createQueue(RandomStringUtils.randomAlphanumeric(20));
- jcsmpSession.provision(queue, new EndpointProperties(), JCSMPSession.WAIT_FOR_CONFIRM);
- } else {
- queue = jcsmpSession.createTemporaryQueue(RandomStringUtils.randomAlphanumeric(20));
- }
-
- flowReceiverContainer = Mockito.spy(new FlowReceiverContainer(jcsmpSession, queue.getName(),
- new EndpointProperties()));
- flowReceiverContainer.bind();
-
String producerManagerKey = UUID.randomUUID().toString();
JCSMPSessionProducerManager producerManager = new JCSMPSessionProducerManager(jcsmpSession);
producer = producerManager.get(producerManagerKey);
@@ -93,66 +66,83 @@ public void setUp() throws Exception {
jcsmpSession.provision(errorQueue, new EndpointProperties(), JCSMPSession.WAIT_FOR_CONFIRM);
}
- @After
- public void tearDown() throws Exception {
+ @AfterEach
+ public void tearDown(JCSMPSession jcsmpSession) throws Exception {
if (producer != null) {
producer.close();
}
- if (flowReceiverContainer != null) {
- Optional.ofNullable(flowReceiverContainer.getFlowReceiverReference())
- .map(FlowReceiverContainer.FlowReceiverReference::get)
- .ifPresent(Consumer::close);
- }
+ Optional.ofNullable(flowReceiverContainerReference.getAndSet(null))
+ .map(FlowReceiverContainer::getFlowReceiverReference)
+ .map(FlowReceiverContainer.FlowReceiverReference::get)
+ .ifPresent(Consumer::close);
if (jcsmpSession != null && !jcsmpSession.isClosed()) {
- if (isDurable) {
- jcsmpSession.deprovision(queue, JCSMPSession.WAIT_FOR_CONFIRM);
- }
jcsmpSession.deprovision(errorQueue, JCSMPSession.WAIT_FOR_CONFIRM);
}
}
- @Test
- public void testHandleSuccess() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testHandleSuccess(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
MessageContainer messageContainer = flowReceiverContainer.receive(5000);
- ErrorQueueRepublishCorrelationKey key = createKey(messageContainer);
+ ErrorQueueRepublishCorrelationKey key = createKey(messageContainer, flowReceiverContainer, isDurable);
key.handleSuccess();
assertTrue(messageContainer.isAcknowledged());
}
- @Test
- public void testHandleSuccessStale() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testHandleSuccessStale(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue)
+ throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
MessageContainer messageContainer = Mockito.spy(flowReceiverContainer.receive(5000));
Mockito.when(messageContainer.isStale()).thenReturn(true);
- ErrorQueueRepublishCorrelationKey key = createKey(messageContainer);
+ ErrorQueueRepublishCorrelationKey key = createKey(messageContainer, flowReceiverContainer, isDurable);
assertThrows(SolaceStaleMessageException.class, key::handleSuccess);
assertEquals(0, key.getErrorQueueDeliveryAttempt());
}
- @Test
- public void testHandleError() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testHandleError(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api)
+ throws Exception {
+ String vpnName = (String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME);
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
MessageContainer messageContainer = flowReceiverContainer.receive(5000);
- ErrorQueueRepublishCorrelationKey key = createKey(messageContainer);
+ ErrorQueueRepublishCorrelationKey key = createKey(messageContainer, flowReceiverContainer, isDurable);
key.handleError(false);
Mockito.verify(errorQueueInfrastructure, Mockito.times(1)).send(messageContainer, key);
assertEquals(1, key.getErrorQueueDeliveryAttempt());
- validateNumEnqueuedMessages(queue, 0);
- validateNumEnqueuedMessages(errorQueue, 1);
+ validateNumEnqueuedMessages(vpnName, queue, 0, sempV2Api);
+ validateNumEnqueuedMessages(vpnName, errorQueue, 1, sempV2Api);
}
- @Test
- public void testHandleErrorRetry() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testHandleErrorRetry(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ String vpnName = (String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME);
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
MessageContainer messageContainer = flowReceiverContainer.receive(5000);
- ErrorQueueRepublishCorrelationKey key = createKey(messageContainer);
+ ErrorQueueRepublishCorrelationKey key = createKey(messageContainer, flowReceiverContainer, isDurable);
Mockito.doAnswer(invocation -> {
if (key.getErrorQueueDeliveryAttempt() < errorQueueInfrastructure.getMaxDeliveryAttempts()) {
@@ -167,15 +157,21 @@ public void testHandleErrorRetry() throws Exception {
int maxDeliveryAttempts = Math.toIntExact(errorQueueInfrastructure.getMaxDeliveryAttempts());
Mockito.verify(errorQueueInfrastructure, Mockito.times(maxDeliveryAttempts)).send(messageContainer, key);
assertEquals(maxDeliveryAttempts, key.getErrorQueueDeliveryAttempt());
- validateNumEnqueuedMessages(queue, 0);
- validateNumEnqueuedMessages(errorQueue, 1);
+ validateNumEnqueuedMessages(vpnName, queue, 0, sempV2Api);
+ validateNumEnqueuedMessages(vpnName, errorQueue, 1, sempV2Api);
}
- @Test
- public void testHandleErrorAsyncRetry() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testHandleErrorAsyncRetry(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ String vpnName = (String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME);
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
MessageContainer messageContainer = flowReceiverContainer.receive(5000);
- ErrorQueueRepublishCorrelationKey key = createKey(messageContainer);
+ ErrorQueueRepublishCorrelationKey key = createKey(messageContainer, flowReceiverContainer, isDurable);
logger.info(String.format("Shutting down ingress for queue %s", errorQueue.getName()));
sempV2Api.config().updateMsgVpnQueue(vpnName, errorQueue.getName(),
@@ -207,15 +203,21 @@ public void testHandleErrorAsyncRetry() throws Exception {
int maxDeliveryAttempts = Math.toIntExact(errorQueueInfrastructure.getMaxDeliveryAttempts());
Mockito.verify(errorQueueInfrastructure, Mockito.times(maxDeliveryAttempts)).send(messageContainer, key);
assertEquals(maxDeliveryAttempts, key.getErrorQueueDeliveryAttempt());
- validateNumEnqueuedMessages(queue, 0);
- validateNumEnqueuedMessages(errorQueue, 1);
+ validateNumEnqueuedMessages(vpnName, queue, 0, sempV2Api);
+ validateNumEnqueuedMessages(vpnName, errorQueue, 1, sempV2Api);
}
- @Test
- public void testHandleErrorRequeueFallback() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testHandleErrorRequeueFallback(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ String vpnName = (String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME);
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
MessageContainer messageContainer = flowReceiverContainer.receive(5000);
- ErrorQueueRepublishCorrelationKey key = createKey(messageContainer);
+ ErrorQueueRepublishCorrelationKey key = createKey(messageContainer, flowReceiverContainer, isDurable);
Mockito.doThrow(new JCSMPException("test")).when(errorQueueInfrastructure).send(messageContainer, key);
@@ -224,25 +226,31 @@ public void testHandleErrorRequeueFallback() throws Exception {
int maxDeliveryAttempts = Math.toIntExact(errorQueueInfrastructure.getMaxDeliveryAttempts());
Mockito.verify(errorQueueInfrastructure, Mockito.times(maxDeliveryAttempts)).send(messageContainer, key);
assertEquals(maxDeliveryAttempts, key.getErrorQueueDeliveryAttempt());
- validateNumEnqueuedMessages(errorQueue, 0);
+ validateNumEnqueuedMessages(vpnName, errorQueue, 0, sempV2Api);
if (isDurable) {
- validateNumEnqueuedMessages(queue, 1);
+ validateNumEnqueuedMessages(vpnName, queue, 1, sempV2Api);
Mockito.verify(flowReceiverContainer).acknowledgeRebind(messageContainer, true);
assertEquals((Long) 2L, sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue.getName(), null)
.getData()
.getBindSuccessCount());
} else {
- validateNumEnqueuedMessages(queue, 0);
+ validateNumEnqueuedMessages(vpnName, queue, 0, sempV2Api);
}
}
- @Test
- public void testHandleErrorRequeueFallbackSkipSync() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testHandleErrorRequeueFallbackSkipSync(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ String vpnName = (String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME);
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
MessageContainer messageContainer = flowReceiverContainer.receive(5000);
- ErrorQueueRepublishCorrelationKey key = createKey(messageContainer);
+ ErrorQueueRepublishCorrelationKey key = createKey(messageContainer, flowReceiverContainer, isDurable);
Mockito.doThrow(new JCSMPException("test")).when(errorQueueInfrastructure).send(messageContainer, key);
@@ -251,10 +259,10 @@ public void testHandleErrorRequeueFallbackSkipSync() throws Exception {
int maxDeliveryAttempts = Math.toIntExact(errorQueueInfrastructure.getMaxDeliveryAttempts());
Mockito.verify(errorQueueInfrastructure, Mockito.times(maxDeliveryAttempts)).send(messageContainer, key);
assertEquals(maxDeliveryAttempts, key.getErrorQueueDeliveryAttempt());
- validateNumEnqueuedMessages(errorQueue, 0);
+ validateNumEnqueuedMessages(vpnName, errorQueue, 0, sempV2Api);
if (isDurable) {
- validateNumEnqueuedMessages(queue, 1);
+ validateNumEnqueuedMessages(vpnName, queue, 1, sempV2Api);
Mockito.verify(retryableTaskService)
.submit(new RetryableAckRebindTask(flowReceiverContainer, messageContainer, retryableTaskService));
Mockito.verify(flowReceiverContainer).acknowledgeRebind(messageContainer, true);
@@ -263,15 +271,21 @@ public void testHandleErrorRequeueFallbackSkipSync() throws Exception {
.getData()
.getBindSuccessCount()));
} else {
- validateNumEnqueuedMessages(queue, 0);
+ validateNumEnqueuedMessages(vpnName, queue, 0, sempV2Api);
}
}
- @Test
- public void testHandleErrorRequeueFallbackFail() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testHandleErrorRequeueFallbackFail(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ String vpnName = (String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME);
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
MessageContainer messageContainer = flowReceiverContainer.receive(5000);
- ErrorQueueRepublishCorrelationKey key = createKey(messageContainer);
+ ErrorQueueRepublishCorrelationKey key = createKey(messageContainer, flowReceiverContainer, isDurable);
Mockito.doThrow(new JCSMPException("test")).when(errorQueueInfrastructure).send(messageContainer, key);
Mockito.doThrow(new JCSMPException("test")).doCallRealMethod().when(flowReceiverContainer)
@@ -282,10 +296,10 @@ public void testHandleErrorRequeueFallbackFail() throws Exception {
int maxDeliveryAttempts = Math.toIntExact(errorQueueInfrastructure.getMaxDeliveryAttempts());
Mockito.verify(errorQueueInfrastructure, Mockito.times(maxDeliveryAttempts)).send(messageContainer, key);
assertEquals(maxDeliveryAttempts, key.getErrorQueueDeliveryAttempt());
- validateNumEnqueuedMessages(errorQueue, 0);
+ validateNumEnqueuedMessages(vpnName, errorQueue, 0, sempV2Api);
if (isDurable) {
- validateNumEnqueuedMessages(queue, 1);
+ validateNumEnqueuedMessages(vpnName, queue, 1, sempV2Api);
Mockito.verify(retryableTaskService)
.submit(new RetryableAckRebindTask(flowReceiverContainer, messageContainer, retryableTaskService));
Mockito.verify(flowReceiverContainer, Mockito.times(2))
@@ -295,22 +309,38 @@ public void testHandleErrorRequeueFallbackFail() throws Exception {
.getData()
.getBindSuccessCount()));
} else {
- validateNumEnqueuedMessages(queue, 0);
+ validateNumEnqueuedMessages(vpnName, queue, 0, sempV2Api);
}
}
- @Test
- public void testHandleErrorStale() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testHandleErrorStale(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue)
+ throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
MessageContainer messageContainer = Mockito.spy(flowReceiverContainer.receive(5000));
Mockito.when(messageContainer.isStale()).thenReturn(true);
- ErrorQueueRepublishCorrelationKey key = createKey(messageContainer);
+ ErrorQueueRepublishCorrelationKey key = createKey(messageContainer, flowReceiverContainer, isDurable);
assertThrows(SolaceStaleMessageException.class, () -> key.handleError(false));
assertEquals(0, key.getErrorQueueDeliveryAttempt());
}
- private ErrorQueueRepublishCorrelationKey createKey(MessageContainer messageContainer) {
+ private FlowReceiverContainer createFlowReceiverContainer(JCSMPSession jcsmpSession, Queue queue)
+ throws JCSMPException {
+ if (flowReceiverContainerReference.compareAndSet(null, Mockito.spy(new FlowReceiverContainer(
+ jcsmpSession, queue.getName(), new EndpointProperties())))) {
+ flowReceiverContainerReference.get().bind();
+ }
+ return flowReceiverContainerReference.get();
+ }
+
+ private ErrorQueueRepublishCorrelationKey createKey(MessageContainer messageContainer,
+ FlowReceiverContainer flowReceiverContainer,
+ boolean isDurable) {
return new ErrorQueueRepublishCorrelationKey(errorQueueInfrastructure,
messageContainer,
flowReceiverContainer,
@@ -318,7 +348,8 @@ private ErrorQueueRepublishCorrelationKey createKey(MessageContainer messageCont
retryableTaskService);
}
- private void validateNumEnqueuedMessages(Queue queue, int expectedCount) throws InterruptedException {
+ private void validateNumEnqueuedMessages(String vpnName, Queue queue, int expectedCount, SempV2Api sempV2Api)
+ throws InterruptedException {
retryAssert(() -> assertThat(sempV2Api.monitor()
.getMsgVpnQueueMsgs(vpnName, queue.getName(), null, null, null, null)
.getData())
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/FlowReceiverContainerIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/FlowReceiverContainerIT.java
index 3f2ee069..9b3e7f35 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/FlowReceiverContainerIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/FlowReceiverContainerIT.java
@@ -2,8 +2,11 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.solace.spring.boot.autoconfigure.SolaceJavaAutoConfiguration;
-import com.solace.spring.cloud.stream.binder.ITBase;
import com.solace.spring.cloud.stream.binder.util.FlowReceiverContainer.FlowReceiverReference;
+import com.solace.test.integration.junit.jupiter.extension.ExecutorServiceExtension;
+import com.solace.test.integration.junit.jupiter.extension.ExecutorServiceExtension.ExecSvc;
+import com.solace.test.integration.junit.jupiter.extension.PubSubPlusExtension;
+import com.solace.test.integration.semp.v2.SempV2Api;
import com.solace.test.integration.semp.v2.action.model.ActionMsgVpnClientDisconnect;
import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnQueue;
import com.solace.test.integration.semp.v2.monitor.ApiException;
@@ -23,24 +26,26 @@
import com.solacesystems.jcsmp.TextMessage;
import com.solacesystems.jcsmp.XMLMessageProducer;
import com.solacesystems.jcsmp.impl.flow.FlowHandle;
-import org.apache.commons.lang.RandomStringUtils;
-import org.apache.commons.lang.math.RandomUtils;
+import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.Timeout;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.junitpioneer.jupiter.cartesian.CartesianTest;
+import org.junitpioneer.jupiter.cartesian.CartesianTest.Values;
import org.mockito.Mockito;
-import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer;
-import org.springframework.test.context.ContextConfiguration;
+import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -57,10 +62,12 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import static com.solace.spring.cloud.stream.binder.test.util.RetryableAssertions.retryAssert;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
@@ -71,68 +78,30 @@
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-
-@RunWith(Parameterized.class)
-@ContextConfiguration(classes = SolaceJavaAutoConfiguration.class,
- initializers = ConfigFileApplicationContextInitializer.class)
-public class FlowReceiverContainerIT extends ITBase {
- @Rule
- public Timeout globalTimeout = new Timeout(5, TimeUnit.MINUTES);
-
- @Parameterized.Parameter
- public String parameterSetName; // Only used for parameter set naming
-
- @Parameterized.Parameter(1)
- public boolean isDurable;
-
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SpringJUnitConfig(classes = SolaceJavaAutoConfiguration.class,
+ initializers = ConfigDataApplicationContextInitializer.class)
+@ExtendWith(ExecutorServiceExtension.class)
+@ExtendWith(PubSubPlusExtension.class)
+@Timeout(value = 5, unit = TimeUnit.MINUTES)
+public class FlowReceiverContainerIT {
private String vpnName;
- private FlowReceiverContainer flowReceiverContainer;
+ private final AtomicReference flowReceiverContainerReference = new AtomicReference<>();
private XMLMessageProducer producer;
- private Queue queue;
private static final Log logger = LogFactory.getLog(FlowReceiverContainerIT.class);
- @Parameterized.Parameters(name = "{0}")
- public static Collection> headerSets() {
- return Arrays.asList(new Object[][]{
- {"Durable", true},
- {"Temporary", false}
- });
- }
-
- @Before
- public void setup() throws Exception {
+ @BeforeEach
+ public void setup(JCSMPSession jcsmpSession) throws Exception {
vpnName = (String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME);
-
- if (isDurable) {
- queue = JCSMPFactory.onlyInstance().createQueue(RandomStringUtils.randomAlphanumeric(20));
- EndpointProperties endpointProperties = new EndpointProperties();
- jcsmpSession.provision(queue, endpointProperties, JCSMPSession.WAIT_FOR_CONFIRM);
- } else {
- queue = jcsmpSession.createTemporaryQueue(RandomStringUtils.randomAlphanumeric(20));
- }
-
- flowReceiverContainer = Mockito.spy(new FlowReceiverContainer(jcsmpSession, queue.getName(),
- new EndpointProperties()));
producer = jcsmpSession.getMessageProducer(new JCSMPStreamingPublishCorrelatingEventHandler() {
-
- @Override
- public void handleError(String s, JCSMPException e, long l) {
- //never called
- }
-
- @Override
- public void responseReceived(String s) {
- //never called
- }
-
@Override
public void responseReceivedEx(Object key) {
logger.debug("Got message with key: " + key);
@@ -145,25 +114,24 @@ public void handleErrorEx(Object o, JCSMPException e, long l) {
});
}
- @After
- public void cleanup() throws Exception {
+ @AfterEach
+ public void cleanup() {
if (producer != null) {
producer.close();
}
- if (flowReceiverContainer != null) {
- Optional.ofNullable(flowReceiverContainer.getFlowReceiverReference())
- .map(FlowReceiverReference::get)
- .ifPresent(Consumer::close);
- }
-
- if (isDurable && jcsmpSession != null && !jcsmpSession.isClosed()) {
- jcsmpSession.deprovision(queue, JCSMPSession.WAIT_FOR_CONFIRM);
- }
+ Optional.ofNullable(flowReceiverContainerReference.getAndSet(null))
+ .map(FlowReceiverContainer::getFlowReceiverReference)
+ .map(FlowReceiverReference::get)
+ .ifPresent(Consumer::close);
}
- @Test
- public void testBind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testBind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
assertNull(flowReceiverContainer.getFlowReceiverReference());
UUID flowReferenceId = flowReceiverContainer.bind();
assertNotNull(flowReferenceId);
@@ -175,26 +143,34 @@ public void testBind() throws Exception {
assertEquals(0, flowReceiverContainer.getNumUnacknowledgedMessages());
}
- @Test
- public void testBindABoundFlow() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testBindABoundFlow(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
assertEquals(flowReferenceId, flowReceiverContainer.bind());
FlowReceiverReference flowReference = flowReceiverContainer.getFlowReceiverReference();
assertNotNull(flowReference);
- MonitorMsgVpnQueue queueInfo = getQueueInfo();
+ MonitorMsgVpnQueue queueInfo = getQueueInfo(sempV2Api, queue);
assertNotNull(queueInfo);
assertEquals((Long) 1L, queueInfo.getBindRequestCount());
assertEquals((Long) 1L, queueInfo.getBindSuccessCount());
- List txFlows = getTxFlows(2, null);
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
assertThat(txFlows, hasSize(1));
assertEquals((Long) ((FlowHandle) flowReference.get()).getFlowId(), txFlows.get(0).getFlowId());
}
- @Test
- public void testBindAnUnboundFlow() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testBindAnUnboundFlow(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
flowReceiverContainer.unbind();
@@ -204,13 +180,19 @@ public void testBindAnUnboundFlow() throws Exception {
FlowReceiverReference flowReference = flowReceiverContainer.getFlowReceiverReference();
assertNotNull(flowReference);
- List txFlows = getTxFlows(2, null);
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
assertThat(txFlows, hasSize(1));
assertEquals((Long) ((FlowHandle) flowReference.get()).getFlowId(), txFlows.get(0).getFlowId());
}
- @Test
- public void testBindWhileRebinding() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testBindWhileRebinding(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue,
+ SempV2Api sempV2Api, @ExecSvc(poolSize = 2) ExecutorService executorService)
+ throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
CountDownLatch midRebindLatch = new CountDownLatch(1);
@@ -222,66 +204,66 @@ public void testBindWhileRebinding() throws Exception {
return null;
}).when(flowReceiverContainer).unbind();
- ExecutorService executorService = Executors.newFixedThreadPool(2);
- try {
- Future rebindFuture = executorService.submit(() -> flowReceiverContainer.rebind(flowReferenceId));
- assertTrue(midRebindLatch.await(1, TimeUnit.MINUTES));
- Future bindFuture = executorService.submit(() -> flowReceiverContainer.bind());
- executorService.shutdown();
+ Future rebindFuture = executorService.submit(() -> flowReceiverContainer.rebind(flowReferenceId));
+ assertTrue(midRebindLatch.await(1, TimeUnit.MINUTES));
+ Future bindFuture = executorService.submit(flowReceiverContainer::bind);
+ executorService.shutdown();
- Thread.sleep(TimeUnit.SECONDS.toMillis(5));
- assertFalse(bindFuture.isDone());
+ Thread.sleep(TimeUnit.SECONDS.toMillis(5));
+ assertFalse(bindFuture.isDone());
- finishRebindLatch.countDown();
- UUID rebindFlowReferenceId = rebindFuture.get(1, TimeUnit.MINUTES);
- assertThat(rebindFlowReferenceId, allOf(notNullValue(), not(equalTo(flowReferenceId))));
+ finishRebindLatch.countDown();
+ UUID rebindFlowReferenceId = rebindFuture.get(1, TimeUnit.MINUTES);
+ assertThat(rebindFlowReferenceId, allOf(notNullValue(), not(equalTo(flowReferenceId))));
- UUID bindFlowReferenceId = bindFuture.get(1, TimeUnit.MINUTES);
- assertThat(bindFlowReferenceId, allOf(notNullValue(),
- not(equalTo(flowReferenceId)), equalTo(rebindFlowReferenceId)));
+ UUID bindFlowReferenceId = bindFuture.get(1, TimeUnit.MINUTES);
+ assertThat(bindFlowReferenceId, allOf(notNullValue(),
+ not(equalTo(flowReferenceId)), equalTo(rebindFlowReferenceId)));
- FlowReceiverReference flowReference = flowReceiverContainer.getFlowReceiverReference();
- assertNotNull(flowReference);
- long currentFlowId = ((FlowHandle) flowReference.get()).getFlowId();
+ FlowReceiverReference flowReference = flowReceiverContainer.getFlowReceiverReference();
+ assertNotNull(flowReference);
+ long currentFlowId = ((FlowHandle) flowReference.get()).getFlowId();
- MonitorMsgVpnQueue queueInfo = getQueueInfo();
- assertNotNull(queueInfo);
- assertEquals((Long) (isDurable ? 2L : 1L), queueInfo.getBindRequestCount());
- assertEquals((Long) (isDurable ? 2L : 1L), queueInfo.getBindSuccessCount());
+ MonitorMsgVpnQueue queueInfo = getQueueInfo(sempV2Api, queue);
+ assertNotNull(queueInfo);
+ assertEquals((Long) (isDurable ? 2L : 1L), queueInfo.getBindRequestCount());
+ assertEquals((Long) (isDurable ? 2L : 1L), queueInfo.getBindSuccessCount());
- List txFlows = getTxFlows(2, null);
- assertThat(txFlows, hasSize(1));
- assertThat(txFlows.get(0).getFlowId(), allOf(notNullValue(), equalTo(currentFlowId)));
- } finally {
- executorService.shutdownNow();
- }
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
+ assertThat(txFlows, hasSize(1));
+ assertThat(txFlows.get(0).getFlowId(), allOf(notNullValue(), equalTo(currentFlowId)));
}
- @Test
- public void testBindWhileReceiving() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testBindWhileReceiving(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue,
+ SempV2Api sempV2Api, @ExecSvc(poolSize = 1) ExecutorService executorService)
+ throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
- ExecutorService executorService = Executors.newSingleThreadExecutor();
- try {
- Future receiveFuture = executorService.submit(() -> flowReceiverContainer.receive());
+ Future receiveFuture = executorService.submit((Callable) flowReceiverContainer::receive);
- // To make sure the flow receive is actually blocked
- Thread.sleep(TimeUnit.SECONDS.toMillis(5));
- assertFalse(receiveFuture.isDone());
+ // To make sure the flow receive is actually blocked
+ Thread.sleep(TimeUnit.SECONDS.toMillis(5));
+ assertFalse(receiveFuture.isDone());
- UUID newFlowReferenceId = flowReceiverContainer.bind();
- assertNotNull(flowReceiverContainer.getFlowReceiverReference());
- assertEquals(flowReferenceId, newFlowReferenceId);
- assertThat(getTxFlows(2, null), hasSize(1));
+ UUID newFlowReferenceId = flowReceiverContainer.bind();
+ assertNotNull(flowReceiverContainer.getFlowReceiverReference());
+ assertEquals(flowReferenceId, newFlowReferenceId);
+ assertThat(getTxFlows(sempV2Api, queue, 2), hasSize(1));
- producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
- assertNotNull(receiveFuture.get(1, TimeUnit.MINUTES));
- } finally {
- executorService.shutdownNow();
- }
+ producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
+ assertNotNull(receiveFuture.get(1, TimeUnit.MINUTES));
}
- @Test
- public void testConcurrentBind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testConcurrentBind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
CyclicBarrier barrier = new CyclicBarrier(30);
ExecutorService executorService = Executors.newFixedThreadPool(barrier.getParties());
try {
@@ -308,12 +290,12 @@ public void testConcurrentBind() throws Exception {
FlowReceiverReference flowReference = flowReceiverContainer.getFlowReceiverReference();
assertNotNull(flowReference);
- MonitorMsgVpnQueue queueInfo = getQueueInfo();
+ MonitorMsgVpnQueue queueInfo = getQueueInfo(sempV2Api, queue);
assertNotNull(queueInfo);
assertEquals((Long) 1L, queueInfo.getBindRequestCount());
assertEquals((Long) 1L, queueInfo.getBindSuccessCount());
- List txFlows = getTxFlows(2, null);
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
assertThat(txFlows, hasSize(1));
assertEquals((Long) ((FlowHandle) flowReference.get()).getFlowId(), txFlows.get(0).getFlowId());
} finally {
@@ -321,43 +303,59 @@ public void testConcurrentBind() throws Exception {
}
}
- @Test
- public void testUnbind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testUnbind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
flowReceiverContainer.unbind();
assertNull(flowReceiverContainer.getFlowReceiverReference());
- assertThat(getTxFlows(1, null), hasSize(0));
+ assertThat(getTxFlows(sempV2Api, queue, 1), hasSize(0));
}
- @Test
- public void testUnbindANonBoundFlow() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testUnbindANonBoundFlow(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.unbind();
if (isDurable) {
- MonitorMsgVpnQueue queueInfo = getQueueInfo();
+ MonitorMsgVpnQueue queueInfo = getQueueInfo(sempV2Api, queue);
assertNotNull(queueInfo);
assertEquals((Long) 0L, queueInfo.getBindRequestCount());
} else {
- assertNull(getQueueInfo());
+ assertNull(getQueueInfo(sempV2Api, queue));
}
}
- @Test
- public void testUnbindAnUnboundFlow() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testUnbindAnUnboundFlow(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
flowReceiverContainer.unbind();
flowReceiverContainer.unbind();
if (isDurable) {
- MonitorMsgVpnQueue queueInfo = getQueueInfo();
+ MonitorMsgVpnQueue queueInfo = getQueueInfo(sempV2Api, queue);
assertNotNull(queueInfo);
assertEquals((Long) 1L, queueInfo.getBindRequestCount());
assertEquals((Long) 1L, queueInfo.getBindSuccessCount());
} else {
- assertNull(getQueueInfo());
+ assertNull(getQueueInfo(sempV2Api, queue));
}
}
- @Test
- public void testUnbindWhileRebinding() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testUnbindWhileRebinding(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
CountDownLatch midRebindLatch = new CountDownLatch(1);
@@ -372,7 +370,7 @@ public void testUnbindWhileRebinding() throws Exception {
try {
Future rebindFuture = executorService.submit(() -> flowReceiverContainer.rebind(flowReferenceId));
assertTrue(midRebindLatch.await(1, TimeUnit.MINUTES));
- Future> unbindFuture = executorService.submit(() -> flowReceiverContainer.unbind());
+ Future> unbindFuture = executorService.submit(flowReceiverContainer::unbind);
executorService.shutdown();
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
@@ -386,7 +384,7 @@ public void testUnbindWhileRebinding() throws Exception {
assertNull(flowReceiverContainer.getFlowReceiverReference());
- MonitorMsgVpnQueue queueInfo = getQueueInfo();
+ MonitorMsgVpnQueue queueInfo = getQueueInfo(sempV2Api, queue);
if (isDurable) {
assertNotNull(queueInfo);
assertEquals((Long) 2L, queueInfo.getBindRequestCount()); // 1 for initial bind, 1 for rebind
@@ -395,7 +393,7 @@ public void testUnbindWhileRebinding() throws Exception {
assertNull(queueInfo);
}
- List txFlows = getTxFlows(1, null);
+ List txFlows = getTxFlows(sempV2Api, queue, 1);
assertNotNull(txFlows);
assertThat(txFlows, hasSize(0));
} finally {
@@ -403,12 +401,16 @@ public void testUnbindWhileRebinding() throws Exception {
}
}
- @Test
- public void testUnbindWhileReceiving() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testUnbindWhileReceiving(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
- Future receiveFuture = executorService.submit(() -> flowReceiverContainer.receive());
+ Future receiveFuture = executorService.submit((Callable) flowReceiverContainer::receive);
// To make sure the flow receive is actually blocked
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
@@ -416,7 +418,7 @@ public void testUnbindWhileReceiving() throws Exception {
flowReceiverContainer.unbind();
assertNull(flowReceiverContainer.getFlowReceiverReference());
- assertThat(getTxFlows(1, null), hasSize(0));
+ assertThat(getTxFlows(sempV2Api, queue, 1), hasSize(0));
ExecutionException exception = assertThrows(ExecutionException.class,
() -> receiveFuture.get(1, TimeUnit.MINUTES));
@@ -427,15 +429,19 @@ public void testUnbindWhileReceiving() throws Exception {
}
}
- @Test
- public void testUnbindWithUnacknowledgedMessage() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testUnbindWithUnacknowledgedMessage(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
List receivedMsgs = new ArrayList<>();
receivedMsgs.add(flowReceiverContainer.receive());
- assertNotNull(receivedMsgs.get(receivedMsgs.size() - 1));
+ assertNotNull(receivedMsgs.get(0));
receivedMsgs.add(flowReceiverContainer.receive());
assertNotNull(receivedMsgs.get(receivedMsgs.size() - 1));
assertEquals(2, flowReceiverContainer.getNumUnacknowledgedMessages());
@@ -445,8 +451,12 @@ public void testUnbindWithUnacknowledgedMessage() throws Exception {
assertTrue(receivedMsgs.stream().allMatch(MessageContainer::isStale));
}
- @Test
- public void testConcurrentUnbind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testConcurrentUnbind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
CyclicBarrier barrier = new CyclicBarrier(30);
@@ -466,13 +476,13 @@ public void testConcurrentUnbind() throws Exception {
future.get(1, TimeUnit.MINUTES);
}
- MonitorMsgVpnQueue queueInfo = getQueueInfo();
+ MonitorMsgVpnQueue queueInfo = getQueueInfo(sempV2Api, queue);
if (isDurable) {
assertNotNull(queueInfo);
assertEquals((Long) 1L, queueInfo.getBindRequestCount());
assertEquals((Long) 1L, queueInfo.getBindSuccessCount());
- List txFlows = getTxFlows(1, null);
+ List txFlows = getTxFlows(sempV2Api, queue, 1);
assertThat(txFlows, hasSize(0));
} else {
assertNull(queueInfo);
@@ -482,14 +492,287 @@ public void testConcurrentUnbind() throws Exception {
}
}
- @Test
- public void testRebind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testPauseResume(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ int defaultWindowSize = (int) jcsmpSession.getProperty(JCSMPProperties.SUB_ACK_WINDOW_SIZE);
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
+ assertThat(getTxFlows(sempV2Api, queue, 2), hasSize(0));
+ flowReceiverContainer.bind();
+ assertEquals(defaultWindowSize, getTxFlows(sempV2Api, queue, 2).get(0).getWindowSize());
+
+ flowReceiverContainer.pause();
+ assertTrue(flowReceiverContainer.isPaused());
+ assertEquals(0, getTxFlows(sempV2Api, queue, 1).get(0).getWindowSize());
+
+ flowReceiverContainer.resume();
+ assertFalse(flowReceiverContainer.isPaused());
+ assertEquals(defaultWindowSize, getTxFlows(sempV2Api, queue, 1).get(0).getWindowSize());
+ }
+
+ @CartesianTest(name = "[{index}] testResuming={0} isDurable={1}")
+ public void testPauseResumeANonBoundFlow(
+ @Values(booleans = {false, true}) boolean testResuming,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
+ if (testResuming) {
+ flowReceiverContainer.pause();
+ }
+
+ assertNull(flowReceiverContainer.getFlowReceiverReference());
+ assertEquals(testResuming, flowReceiverContainer.isPaused());
+
+ if (testResuming) {
+ flowReceiverContainer.resume();
+ } else {
+ flowReceiverContainer.pause();
+ }
+ assertNull(flowReceiverContainer.getFlowReceiverReference());
+ assertEquals(!testResuming, flowReceiverContainer.isPaused());
+ assertEquals(0, getTxFlows(sempV2Api, queue, 1).size());
+ }
+
+ @CartesianTest(name = "[{index}] testResuming={0} isDurable={1}")
+ public void testPauseResumeAnUnboundFlow(
+ @Values(booleans = {false, true}) boolean testResuming,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
+ flowReceiverContainer.bind();
+ flowReceiverContainer.unbind();
+
+ if (testResuming) {
+ flowReceiverContainer.pause();
+ }
+
+ assertNull(flowReceiverContainer.getFlowReceiverReference());
+ assertEquals(testResuming, flowReceiverContainer.isPaused());
+ if (testResuming) {
+ flowReceiverContainer.resume();
+ } else {
+ flowReceiverContainer.pause();
+ }
+ assertNull(flowReceiverContainer.getFlowReceiverReference());
+ assertEquals(!testResuming, flowReceiverContainer.isPaused());
+ assertEquals(0, getTxFlows(sempV2Api, queue, 1).size());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindWhilePaused(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ int defaultWindowSize = (int) jcsmpSession.getProperty(JCSMPProperties.SUB_ACK_WINDOW_SIZE);
+ assertThat(getTxFlows(sempV2Api, queue, 2), hasSize(0));
+
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+ UUID flowReferenceId1 = flowReceiverContainer.bind();
+ List flows = getTxFlows(sempV2Api, queue, 2);
+ assertThat(flows, hasSize(1));
+ assertEquals(defaultWindowSize, flows.get(0).getWindowSize());
+
+ flowReceiverContainer.pause();
+ assertTrue(flowReceiverContainer.isPaused());
+ assertEquals(0, getTxFlows(sempV2Api, queue, 1).get(0).getWindowSize());
+
+ UUID flowReferenceId2 = flowReceiverContainer.rebind(flowReferenceId1);
+ assertNotEquals(flowReferenceId1, flowReferenceId2);
+
+ //Check paused state was preserved post rebind
+ assertTrue(flowReceiverContainer.isPaused());
+ flows = getTxFlows(sempV2Api, queue, 2);
+ assertThat(flows, hasSize(1));
+ assertEquals(0, flows.get(0).getWindowSize());
+
+ //Resume
+ flowReceiverContainer.resume();
+ assertFalse(flowReceiverContainer.isPaused());
+ flows = getTxFlows(sempV2Api, queue, 2);
+ assertThat(flows, hasSize(1));
+ assertEquals(defaultWindowSize, flows.get(0).getWindowSize());
+ }
+
+ @CartesianTest(name = "[{index}] testResuming={0} isDurable={1}")
+ public void testPauseResumeWhileRebinding(
+ @Values(booleans = {false, true}) boolean testResuming,
+ @Values(booleans = {false, true}) boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
+ UUID flowReferenceId = flowReceiverContainer.bind();
+ if (testResuming) flowReceiverContainer.pause();
+
+ CountDownLatch midRebindLatch = new CountDownLatch(1);
+ CountDownLatch finishRebindLatch = new CountDownLatch(1);
+ Mockito.doAnswer(invocation -> {
+ midRebindLatch.countDown();
+ finishRebindLatch.await();
+ return invocation.callRealMethod();
+ }).when(flowReceiverContainer).bind();
+
+ ExecutorService executorService = Executors.newFixedThreadPool(2);
+ try {
+ assertEquals(testResuming, flowReceiverContainer.isPaused());
+
+ Future rebindFuture = executorService.submit(() -> flowReceiverContainer.rebind(flowReferenceId));
+ assertTrue(midRebindLatch.await(1, TimeUnit.MINUTES));
+ Future> pauseResumeFuture = executorService.submit(testResuming ? () -> {
+ try {
+ flowReceiverContainer.resume();
+ } catch (JCSMPException e) {
+ throw new RuntimeException(e);
+ }
+ } : flowReceiverContainer::pause);
+ executorService.shutdown();
+
+ Thread.sleep(TimeUnit.SECONDS.toMillis(5));
+ assertFalse(rebindFuture.isDone());
+ assertFalse(pauseResumeFuture.isDone());
+ assertEquals(testResuming, flowReceiverContainer.isPaused());
+
+ finishRebindLatch.countDown();
+ pauseResumeFuture.get(1, TimeUnit.MINUTES);
+
+ assertEquals(testResuming, !flowReceiverContainer.isPaused()); //Pause state has flipped
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
+ assertThat(txFlows, hasSize(1));
+ int defaultWindowSize = (int) jcsmpSession.getProperty(JCSMPProperties.SUB_ACK_WINDOW_SIZE);
+ assertEquals(testResuming ? defaultWindowSize : 0, txFlows.get(0).getWindowSize());
+ } finally {
+ executorService.shutdownNow();
+ }
+ }
+
+ @ParameterizedTest(name = "[{index}] isDurable={0}")
+ @ValueSource(booleans = {false, true})
+ public void testPauseWhileResuming(
+ boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api,
+ @ExecSvc ExecutorService executorService) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
+ flowReceiverContainer.bind();
+ flowReceiverContainer.pause();
+ assertTrue(flowReceiverContainer.isPaused());
+
+ CountDownLatch midResumeLatch = new CountDownLatch(1);
+ // can't use latch, or else pause() will take write lock
+ CountDownLatch finishResumeLatch = new CountDownLatch(1);
+ Mockito.doAnswer(invocation -> {
+ // Call real method first since the potential race condition can happen right after
+ // this method returns in flowReceiverContainer.resume()
+ Object toReturn = invocation.callRealMethod();
+ midResumeLatch.countDown();
+ finishResumeLatch.await();
+ return toReturn;
+ }).when(flowReceiverContainer).doFlowReceiverReferenceResume();
+
+ Future> resumeFuture = executorService.submit(() -> {
+ try {
+ flowReceiverContainer.resume();
+ } catch (JCSMPException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ assertTrue(midResumeLatch.await(1, TimeUnit.MINUTES));
+ Future> pauseFuture = executorService.submit(flowReceiverContainer::pause);
+ executorService.shutdown();
+
+ Thread.sleep(TimeUnit.SECONDS.toMillis(5));
+ assertFalse(resumeFuture.isDone());
+ assertFalse(pauseFuture.isDone());
+ assertTrue(flowReceiverContainer.isPaused());
+
+ finishResumeLatch.countDown();
+ pauseFuture.get(1, TimeUnit.MINUTES);
+ resumeFuture.get(1, TimeUnit.MINUTES);
+ assertTrue(flowReceiverContainer.isPaused());
+
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
+ assertThat(txFlows, hasSize(1));
+ assertEquals(0, txFlows.get(0).getWindowSize());
+ }
+
+ @ParameterizedTest(name = "[{index}] isDurable={0}")
+ @ValueSource(booleans = {false, true})
+ public void testResumeWhilePausing(
+ boolean isDurable,
+ JCSMPSession jcsmpSession,
+ Queue durableQueue,
+ SempV2Api sempV2Api,
+ @ExecSvc ExecutorService executorService) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
+ flowReceiverContainer.bind();
+ assertFalse(flowReceiverContainer.isPaused());
+
+ CountDownLatch midPauseLatch = new CountDownLatch(1);
+ CountDownLatch finishPauseLatch = new CountDownLatch(1);
+ Mockito.doAnswer(invocation -> {
+ // Call real method first since the potential race condition can happen right after
+ // this method returns in flowReceiverContainer.pause()
+ Object toReturn = invocation.callRealMethod();
+ midPauseLatch.countDown();
+ finishPauseLatch.await();
+ return toReturn;
+ }).when(flowReceiverContainer).doFlowReceiverReferencePause();
+
+ Future> pauseFuture = executorService.submit(flowReceiverContainer::pause);
+ assertTrue(midPauseLatch.await(1, TimeUnit.MINUTES));
+ Future> resumeFuture = executorService.submit(() -> {
+ try {
+ flowReceiverContainer.resume();
+ } catch (JCSMPException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ executorService.shutdown();
+
+ Thread.sleep(TimeUnit.SECONDS.toMillis(5));
+ assertFalse(pauseFuture.isDone());
+ assertFalse(resumeFuture.isDone());
+ assertFalse(flowReceiverContainer.isPaused());
+
+ finishPauseLatch.countDown();
+ resumeFuture.get(1, TimeUnit.MINUTES);
+ pauseFuture.get(1, TimeUnit.MINUTES);
+ assertFalse(flowReceiverContainer.isPaused());
+
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
+ assertThat(txFlows, hasSize(1));
+ int defaultWindowSize = (int) jcsmpSession.getProperty(JCSMPProperties.SUB_ACK_WINDOW_SIZE);
+ assertEquals(defaultWindowSize, txFlows.get(0).getWindowSize());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId1 = flowReceiverContainer.bind();
FlowReceiverReference flowReference1 = flowReceiverContainer.getFlowReceiverReference();
assertNotNull(flowReference1);
- List txFlows1 = getTxFlows(2, null);
+ List txFlows1 = getTxFlows(sempV2Api, queue, 2);
assertThat(txFlows1, hasSize(1));
assertEquals((Long) ((FlowHandle) flowReference1.get()).getFlowId(), txFlows1.get(0).getFlowId());
@@ -510,27 +793,35 @@ public void testRebind() throws Exception {
assertEquals(flowReference1.get().getDestination(), flowReference2.get().getDestination());
assertEquals(flowReference1.get().getEndpoint(), flowReference2.get().getEndpoint());
- List txFlows2 = getTxFlows(2, null);
+ List txFlows2 = getTxFlows(sempV2Api, queue, 2);
assertThat(txFlows2, hasSize(1));
assertEquals((Long) ((FlowHandle) flowReference2.get()).getFlowId(), txFlows2.get(0).getFlowId());
}
- @Test
- public void testRebindANonBoundFlow() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindANonBoundFlow(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UnboundFlowReceiverContainerException exception = assertThrows(UnboundFlowReceiverContainerException.class,
() -> flowReceiverContainer.rebind(UUID.randomUUID()));
assertThat(exception.getMessage(), containsString("is not bound"));
if (isDurable) {
- MonitorMsgVpnQueue queueInfo = getQueueInfo();
+ MonitorMsgVpnQueue queueInfo = getQueueInfo(sempV2Api, queue);
assertNotNull(queueInfo);
assertEquals((Long) 0L, queueInfo.getBindRequestCount());
} else {
- assertNull(getQueueInfo());
+ assertNull(getQueueInfo(sempV2Api, queue));
}
}
- @Test
- public void testRebindAReboundFlow() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindAReboundFlow(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
TextMessage message = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
flowReceiverContainer.bind();
@@ -547,12 +838,16 @@ public void testRebindAReboundFlow() throws Exception {
flowReceiverContainer.rebind(receivedMessage.getFlowReceiverReferenceId()));
}
- @Test
- public void testRebindWhileReceiving() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindWhileReceiving(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
- Future receiveFuture = executorService.submit(() -> flowReceiverContainer.receive());
+ Future receiveFuture = executorService.submit((Callable) flowReceiverContainer::receive);
// To make sure the flow receive is actually blocked
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
@@ -561,7 +856,7 @@ public void testRebindWhileReceiving() throws Exception {
UUID newFlowReferenceId = flowReceiverContainer.rebind(flowReferenceId);
assertNotNull(flowReceiverContainer.getFlowReceiverReference());
assertNotEquals(flowReferenceId, newFlowReferenceId);
- assertThat(getTxFlows(2, null), hasSize(1));
+ assertThat(getTxFlows(sempV2Api, queue, 2), hasSize(1));
assertNull(receiveFuture.get(1, TimeUnit.MINUTES));
} finally {
@@ -569,8 +864,12 @@ public void testRebindWhileReceiving() throws Exception {
}
}
- @Test
- public void testRebindWithUnacknowledgedMessage() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindWithUnacknowledgedMessage(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
@@ -602,8 +901,12 @@ public void testRebindWithUnacknowledgedMessage() throws Exception {
}
}
- @Test
- public void testRebindWithTimeout() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindWithTimeout(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.setRebindWaitTimeout(1, TimeUnit.SECONDS);
UUID flowReferenceId = flowReceiverContainer.bind();
@@ -618,8 +921,12 @@ public void testRebindWithTimeout() throws Exception {
assertThrows(SolaceStaleMessageException.class, () -> flowReceiverContainer.acknowledge(receivedMessage));
}
- @Test
- public void testRebindReturnImmediately() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindReturnImmediately(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.setRebindWaitTimeout(-1, TimeUnit.SECONDS); // block forever
UUID flowReferenceId = flowReceiverContainer.bind();
@@ -639,8 +946,12 @@ public void testRebindReturnImmediately() throws Exception {
}
}
- @Test
- public void testRebindAckReturnImmediately() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindAckReturnImmediately(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.setRebindWaitTimeout(-1, TimeUnit.SECONDS); // block forever
UUID flowReferenceId = flowReceiverContainer.bind();
@@ -667,8 +978,12 @@ public void testRebindAckReturnImmediately() throws Exception {
}
}
- @Test
- public void testRebindAckAlreadyAcknowledged() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindAckAlreadyAcknowledged(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
MessageContainer receivedMessage = flowReceiverContainer.receive();
@@ -677,8 +992,12 @@ public void testRebindAckAlreadyAcknowledged() throws Exception {
assertEquals(flowReferenceId, flowReceiverContainer.acknowledgeRebind(receivedMessage));
}
- @Test
- public void testRebindAckAlreadyAcknowledgedBlocked() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindAckAlreadyAcknowledgedBlocked(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
@@ -707,8 +1026,12 @@ public void testRebindAckAlreadyAcknowledgedBlocked() throws Exception {
}
}
- @Test
- public void testRebindAckAlreadyAcknowledgedAndReturnImmediately() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindAckAlreadyAcknowledgedAndReturnImmediately(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
@@ -733,8 +1056,12 @@ public void testRebindAckAlreadyAcknowledgedAndReturnImmediately() throws Except
}
}
- @Test
- public void testRebindInterrupt() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindInterrupt(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
FlowReceiverReference flowReference = flowReceiverContainer.getFlowReceiverReference();
@@ -780,18 +1107,15 @@ public void testRebindInterrupt() throws Exception {
// Give some time for the messages to be acknowledged off the broker
Thread.sleep(TimeUnit.SECONDS.toMillis(3));
- List txFlows = getTxFlows(2, null);
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
assertThat(txFlows, hasSize(1));
assertEquals((Long) 2L, txFlows.get(0).getAckedMsgCount());
assertEquals((Long) 0L, txFlows.get(0).getUnackedMsgCount());
}
@Test
- public void testRebindAfterFlowDisconnect() throws Exception {
- if (!isDurable) {
- logger.info("Test does not apply for non-durable queues");
- return;
- }
+ public void testRebindAfterFlowDisconnect(JCSMPSession jcsmpSession, Queue queue, SempV2Api sempV2Api) throws Exception {
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
flowReceiverContainer.bind();
@@ -811,11 +1135,8 @@ public void testRebindAfterFlowDisconnect() throws Exception {
}
@Test
- public void testRebindAfterFlowReconnect() throws Exception {
- if (!isDurable) {
- logger.info("Test does not apply for non-durable queues");
- return;
- }
+ public void testRebindAfterFlowReconnect(JCSMPSession jcsmpSession, Queue queue, SempV2Api sempV2Api) throws Exception {
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
TextMessage message = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
@@ -853,7 +1174,7 @@ public void testRebindAfterFlowReconnect() throws Exception {
Mockito.verify(flowReceiverContainer, Mockito.times(2)).bind(); // +1 for init bind
retryAssert(() -> {
- List txFlows = getTxFlows(2, null);
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
assertThat(txFlows, hasSize(1));
assertEquals((Long) 0L, txFlows.get(0).getAckedMsgCount());
assertEquals((Long) 1L, txFlows.get(0).getUnackedMsgCount());
@@ -861,8 +1182,13 @@ public void testRebindAfterFlowReconnect() throws Exception {
});
}
- @Test
- public void testRebindAfterSessionReconnect() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testRebindAfterSessionReconnect(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
TextMessage message = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
UUID flowReferenceId = flowReceiverContainer.bind();
@@ -873,7 +1199,7 @@ public void testRebindAfterSessionReconnect() throws Exception {
String clientName = (String) jcsmpSession.getProperty(JCSMPProperties.CLIENT_NAME);
- logger.info(String.format("Remotely disconnecting session %s", jcsmpSession.getSessionName()));
+ logger.info(String.format("Remotely disconnecting client %s", clientName));
sempV2Api.action().doMsgVpnClientDisconnect(vpnName, clientName, new ActionMsgVpnClientDisconnect());
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
@@ -893,15 +1219,19 @@ public void testRebindAfterSessionReconnect() throws Exception {
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
- List txFlows = getTxFlows(2, null);
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
assertThat(txFlows, hasSize(1));
assertEquals((Long) 0L, txFlows.get(0).getAckedMsgCount());
assertEquals((Long) 1L, txFlows.get(0).getUnackedMsgCount());
assertEquals(isDurable ? (Long) 1L : (Long) 0L, txFlows.get(0).getRedeliveredMsgCount());
}
- @Test
- public void testConcurrentRebind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testConcurrentRebind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
CyclicBarrier barrier = new CyclicBarrier(30);
UUID flowReferenceId = flowReceiverContainer.bind();
@@ -913,7 +1243,7 @@ public void testConcurrentRebind() throws Exception {
barrier.await();
return flowReceiverContainer.rebind(flowReferenceId);
})
- .map(c -> executorService.schedule(c, RandomUtils.nextInt(100), TimeUnit.MILLISECONDS))
+ .map(c -> executorService.schedule(c, RandomUtils.nextInt(0, 100), TimeUnit.MILLISECONDS))
.collect(Collectors.toSet());
executorService.shutdown();
@@ -929,8 +1259,12 @@ public void testConcurrentRebind() throws Exception {
}
}
- @Test
- public void testReceive() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceive(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
TextMessage message = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
UUID flowReferenceId1 = flowReceiverContainer.bind();
@@ -943,17 +1277,25 @@ public void testReceive() throws Exception {
assertEquals(1, flowReceiverContainer.getNumUnacknowledgedMessages());
}
- @Test
- public void testReceiveOnANonBoundFlow() {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveOnANonBoundFlow(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
long startTime = System.currentTimeMillis();
UnboundFlowReceiverContainerException exception = assertThrows(UnboundFlowReceiverContainerException.class,
- () -> flowReceiverContainer.receive());
+ flowReceiverContainer::receive);
assertThat(System.currentTimeMillis() - startTime, greaterThanOrEqualTo(TimeUnit.SECONDS.toMillis(5)));
assertThat(exception.getMessage(), containsString("is not bound"));
}
- @Test
- public void testReceiveWhileRebinding() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveWhileRebinding(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
TextMessage message = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
UUID flowReferenceId = flowReceiverContainer.bind();
producer.send(message, queue);
@@ -970,7 +1312,7 @@ public void testReceiveWhileRebinding() throws Exception {
try {
Future rebindFuture = executorService.submit(() -> flowReceiverContainer.rebind(flowReferenceId));
assertTrue(midRebindLatch.await(1, TimeUnit.MINUTES));
- Future receiveFuture = executorService.submit(() -> flowReceiverContainer.receive());
+ Future receiveFuture = executorService.submit((Callable) flowReceiverContainer::receive);
executorService.shutdown();
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
@@ -990,8 +1332,12 @@ public void testReceiveWhileRebinding() throws Exception {
}
}
- @Test
- public void testReceiveWhilePreRebinding() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveWhilePreRebinding(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
TextMessage message = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
UUID flowReferenceId = flowReceiverContainer.bind();
producer.send(message, queue);
@@ -1005,7 +1351,7 @@ public void testReceiveWhilePreRebinding() throws Exception {
Thread.sleep(TimeUnit.SECONDS.toMillis(3));
assertFalse(rebindFuture.isDone());
- Future receiveFuture = executorService.submit(() -> flowReceiverContainer.receive());
+ Future receiveFuture = executorService.submit((Callable) flowReceiverContainer::receive);
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
assertFalse(receiveFuture.isDone());
assertFalse(rebindFuture.isDone());
@@ -1028,14 +1374,22 @@ public void testReceiveWhilePreRebinding() throws Exception {
}
}
- @Test
- public void testReceiveNoWait() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveNoWait(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
assertNull(flowReceiverContainer.receive(0));
}
- @Test
- public void testReceiveWithTimeout() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveWithTimeout(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
TextMessage message = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
UUID flowReferenceId = flowReceiverContainer.bind();
@@ -1048,32 +1402,48 @@ public void testReceiveWithTimeout() throws Exception {
assertEquals(flowReferenceId, messageReceived.getFlowReceiverReferenceId());
}
- @Test
- public void testReceiveElapsedTimeout() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveElapsedTimeout(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
assertNull(flowReceiverContainer.receive(1));
}
- @Test
- public void testReceiveNegativeTimeout() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveNegativeTimeout(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
assertNull(flowReceiverContainer.receive(-1));
}
- @Test
- public void testReceiveZeroTimeout() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveZeroTimeout(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
assertNull(flowReceiverContainer.receive(0));
}
- @Test
- public void testReceiveWithDelayedBind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveWithDelayedBind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
long startTime = System.currentTimeMillis();
- Future future = executorService.submit(() -> flowReceiverContainer.receive());
+ Future future = executorService.submit((Callable) flowReceiverContainer::receive);
Thread.sleep(TimeUnit.SECONDS.toMillis(2));
assertFalse(future.isDone());
@@ -1091,8 +1461,12 @@ public void testReceiveWithDelayedBind() throws Exception {
}
}
- @Test
- public void testReceiveWithTimeoutAndDelayedBind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveWithTimeoutAndDelayedBind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
ExecutorService executorService = Executors.newSingleThreadExecutor();
@@ -1123,12 +1497,16 @@ public void testReceiveWithTimeoutAndDelayedBind() throws Exception {
}
}
- @Test
- public void testReceiveInterrupt() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveInterrupt(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
- Future receiveFuture = executorService.submit(() -> flowReceiverContainer.receive());
+ Future receiveFuture = executorService.submit((Callable) flowReceiverContainer::receive);
executorService.shutdown();
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
@@ -1142,17 +1520,14 @@ public void testReceiveInterrupt() throws Exception {
}
@Test
- public void testReceiveInterruptedByFlowReconnect() throws Exception {
- if (!isDurable) {
- logger.info("Test does not apply for non-durable queues");
- return;
- }
+ public void testReceiveInterruptedByFlowReconnect(JCSMPSession jcsmpSession, Queue queue, SempV2Api sempV2Api) throws Exception {
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
flowReceiverContainer.bind();
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
- Future receiveFuture = executorService.submit(() -> flowReceiverContainer.receive());
+ Future receiveFuture = executorService.submit((Callable) flowReceiverContainer::receive);
executorService.shutdown();
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
@@ -1183,21 +1558,26 @@ public void testReceiveInterruptedByFlowReconnect() throws Exception {
}
}
- @Test
- public void testReceiveInterruptedBySessionReconnect() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveInterruptedBySessionReconnect(boolean isDurable, JCSMPSession jcsmpSession,
+ Queue durableQueue, SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
String clientName = (String) jcsmpSession.getProperty(JCSMPProperties.CLIENT_NAME);
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
- Future receiveFuture = executorService.submit(() -> flowReceiverContainer.receive());
+ Future receiveFuture = executorService.submit((Callable) flowReceiverContainer::receive);
executorService.shutdown();
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
assertFalse(receiveFuture.isDone());
- logger.info(String.format("Remotely disconnecting session %s", jcsmpSession.getSessionName()));
+ logger.info(String.format("Remotely disconnecting client %s", clientName));
sempV2Api.action().doMsgVpnClientDisconnect(vpnName, clientName, new ActionMsgVpnClientDisconnect());
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
@@ -1210,8 +1590,12 @@ public void testReceiveInterruptedBySessionReconnect() throws Exception {
}
}
- @Test
- public void testReceiveAfterRebind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReceiveAfterRebind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
TextMessage message = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
UUID flowReferenceId1 = flowReceiverContainer.bind();
@@ -1237,8 +1621,12 @@ public void testReceiveAfterRebind() throws Exception {
assertEquals(1, flowReceiverContainer.getNumUnacknowledgedMessages());
}
- @Test
- public void testWaitForBind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testWaitForBind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
Future future = executorService.submit(() ->
@@ -1252,20 +1640,32 @@ public void testWaitForBind() throws Exception {
}
}
- @Test
- public void testWaitForBindNegative() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testWaitForBindNegative(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
long startTime = System.currentTimeMillis();
assertFalse(flowReceiverContainer.waitForBind(-100));
assertThat(System.currentTimeMillis() - startTime, lessThan(500L));
}
- @Test
- public void testAcknowledgeNull() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testAcknowledgeNull(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.acknowledge(null);
}
- @Test
- public void testAcknowledgeAfterUnbind() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testAcknowledgeAfterUnbind(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
flowReceiverContainer.bind();
producer.send(JCSMPFactory.onlyInstance().createMessage(TextMessage.class), queue);
@@ -1280,11 +1680,8 @@ public void testAcknowledgeAfterUnbind() throws Exception {
}
@Test
- public void testAcknowledgeAfterFlowReconnect() throws Exception {
- if (!isDurable) {
- logger.info("Test does not apply for non-durable queues");
- return;
- }
+ public void testAcknowledgeAfterFlowReconnect(JCSMPSession jcsmpSession, Queue queue, SempV2Api sempV2Api) throws Exception {
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
TextMessage message = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
@@ -1319,15 +1716,20 @@ public void testAcknowledgeAfterFlowReconnect() throws Exception {
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
- List txFlows = getTxFlows(2, null);
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
assertThat(txFlows, hasSize(1));
assertEquals((Long) 1L, txFlows.get(0).getAckedMsgCount());
assertEquals((Long) 0L, txFlows.get(0).getUnackedMsgCount());
assertEquals((Long) 1L, txFlows.get(0).getRedeliveredMsgCount());
}
- @Test
- public void testAcknowledgeAfterSessionReconnect() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testAcknowledgeAfterSessionReconnect(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue,
+ SempV2Api sempV2Api) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
TextMessage message = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
flowReceiverContainer.bind();
@@ -1338,7 +1740,7 @@ public void testAcknowledgeAfterSessionReconnect() throws Exception {
String clientName = (String) jcsmpSession.getProperty(JCSMPProperties.CLIENT_NAME);
- logger.info(String.format("Remotely disconnecting session %s", jcsmpSession.getSessionName()));
+ logger.info(String.format("Remotely disconnecting client %s", clientName));
sempV2Api.action().doMsgVpnClientDisconnect(vpnName, clientName, new ActionMsgVpnClientDisconnect());
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
@@ -1349,20 +1751,25 @@ public void testAcknowledgeAfterSessionReconnect() throws Exception {
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
- List txFlows = getTxFlows(2, null);
+ List txFlows = getTxFlows(sempV2Api, queue, 2);
assertThat(txFlows, hasSize(1));
assertEquals((Long) 1L, txFlows.get(0).getAckedMsgCount());
assertEquals((Long) 0L, txFlows.get(0).getUnackedMsgCount());
assertEquals((Long) 1L, txFlows.get(0).getRedeliveredMsgCount());
}
- @Test
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ @Execution(ExecutionMode.SAME_THREAD)
// @Repeat(10) // should run a few times to make sure its stable
- public void testConcurrentAll() throws Exception {
+ public void testConcurrentAll(boolean isDurable, JCSMPSession jcsmpSession, Queue durableQueue) throws Exception {
+ Queue queue = isDurable ? durableQueue : jcsmpSession.createTemporaryQueue();
+ FlowReceiverContainer flowReceiverContainer = createFlowReceiverContainer(jcsmpSession, queue);
+
UUID flowReferenceId = flowReceiverContainer.bind();
Callable>[] actions = new Callable[]{
- (Callable>) () -> flowReceiverContainer.bind(),
+ (Callable>) flowReceiverContainer::bind,
(Callable>) () -> {flowReceiverContainer.unbind(); return null;},
(Callable>) () -> {
try {
@@ -1436,7 +1843,7 @@ public void testConcurrentAll() throws Exception {
barrier.await();
return action.call();
}))
- .map(c -> executorService.schedule(c, RandomUtils.nextInt(100), TimeUnit.MILLISECONDS))
+ .map(c -> executorService.schedule(c, RandomUtils.nextInt(0, 100), TimeUnit.MILLISECONDS))
.collect(Collectors.toSet());
for (ScheduledFuture> future : futures) {
@@ -1447,26 +1854,34 @@ public void testConcurrentAll() throws Exception {
}
}
- private MonitorMsgVpnQueue getQueueInfo() throws ApiException, JsonProcessingException {
+ private FlowReceiverContainer createFlowReceiverContainer(JCSMPSession jcsmpSession, Queue queue) {
+ if (flowReceiverContainerReference.compareAndSet(null, Mockito.spy(new FlowReceiverContainer(
+ jcsmpSession, queue.getName(), new EndpointProperties())))) {
+ logger.info("Created new FlowReceiverContainer " + flowReceiverContainerReference.get().getId());
+ }
+ return flowReceiverContainerReference.get();
+ }
+
+ private MonitorMsgVpnQueue getQueueInfo(SempV2Api sempV2Api, Queue queue) throws ApiException, JsonProcessingException {
try {
return sempV2Api.monitor().getMsgVpnQueue(vpnName, queue.getName(), null).getData();
} catch (ApiException e) {
- return processApiException(e);
+ return processApiException(sempV2Api, e);
}
}
- private List