From f3e91524824b790fec9cab1f2b1d7dd86fb5c30a Mon Sep 17 00:00:00 2001 From: Rivaldi Date: Fri, 31 Oct 2025 08:50:26 +0700 Subject: [PATCH] feat: pull unique-enq branch changes to 2.x --- .../com/github/sonus21/rqueue/core/Job.java | 36 +- .../rqueue/core/RqueueMessageEnqueuer.java | 275 +++++++-------- .../rqueue/core/RqueueMessageManager.java | 71 ++-- .../rqueue/core/impl/BaseMessageSender.java | 49 +-- .../ReactiveRqueueMessageEnqueuerImpl.java | 216 +++++------- .../core/impl/RqueueMessageEnqueuerImpl.java | 115 ++++--- .../core/impl/RqueueMessageManagerImpl.java | 67 ++-- .../core/impl/RqueueMessageSenderImpl.java | 13 +- .../rqueue/dao/RqueueMessageMetadataDao.java | 22 +- .../impl/RqueueMessageMetadataDaoImpl.java | 41 ++- .../exception/DuplicateMessageException.java | 24 ++ .../sonus21/rqueue/listener/JobImpl.java | 28 +- .../sonus21/rqueue/utils/Constants.java | 26 +- .../service/RqueueMessageMetadataService.java | 27 +- .../RqueueMessageMetadataServiceImpl.java | 57 ++-- .../impl/RqueueMessageManagerImplTest.java | 315 +++++++++++++++--- .../sonus21/rqueue/listener/JobImplTest.java | 88 ++--- .../utils/MessageMetadataTestUtils.java | 45 +++ .../rqueue/utils/RqueueMessageTestUtils.java | 37 ++ .../RqueueMessageMetadataServiceTest.java | 78 +++-- .../integration/MessageDeduplicationTest.java | 37 +- .../integration/MessageProcessorTest.java | 27 +- .../RqueueMessageTemplateTest.java | 21 +- .../ApplicationWithMessageProcessor.java | 24 +- .../rqueue/test/common/SpringTestBase.java | 84 +---- .../test/service/ConsumedMessageStore.java | 54 +-- .../rqueue/test/service/FailureManager.java | 23 +- .../sonus21/rqueue/test/tests/RetryTests.java | 22 +- .../services/FailureDetailRepositoryImpl.java | 44 ++- .../tests/integration/SpringAppTest.java | 152 +++++---- .../tests/unit/RqueueMessageConfigTest.java | 38 ++- 31 files changed, 1307 insertions(+), 849 deletions(-) create mode 100644 rqueue-core/src/main/java/com/github/sonus21/rqueue/exception/DuplicateMessageException.java create mode 100644 rqueue-core/src/test/java/com/github/sonus21/rqueue/utils/MessageMetadataTestUtils.java create mode 100644 rqueue-core/src/test/java/com/github/sonus21/rqueue/utils/RqueueMessageTestUtils.java diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/Job.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/Job.java index 07899d55..840d10c0 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/Job.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/Job.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2021-2023 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -27,10 +27,10 @@ /** * On each execution Rqueue creates a job to track it's status and execution progress. * - *

A job belongs to a single message poll, each message listener call creates an execution {@link - * com.github.sonus21.rqueue.models.db.Execution} and that has a detail for specific execution. - * Overall job status can be found using this job interface. This object is available via {@link - * org.springframework.messaging.handler.annotation.Header} in listener method. + *

A job belongs to a single message poll, each message listener call creates an execution + * {@link com.github.sonus21.rqueue.models.db.Execution} and that has a detail for specific + * execution. Overall job status can be found using this job interface. This object is available via + * {@link org.springframework.messaging.handler.annotation.Header} in listener method. */ public interface Job { @@ -81,7 +81,7 @@ public interface Job { * return zero value * * @return remaining duration that this job can take, otherwise other listener will consume this - * message + * message */ Duration getVisibilityTimeout(); @@ -181,8 +181,8 @@ public interface Job { * Release this job back to the queue, the released job would be available for re-execution after * the duration time. * - * @param status job status - * @param why why do want to release this job + * @param status job status + * @param why why do want to release this job * @param duration any positive duration */ void release(JobStatus status, Serializable why, Duration duration); @@ -191,7 +191,7 @@ public interface Job { * Release this job back to queue, this job available for execution after one second. * * @param status what should be the job status - * @param why why do you want to delete this job + * @param why why do you want to delete this job */ void release(JobStatus status, Serializable why); @@ -199,7 +199,7 @@ public interface Job { * Delete this job * * @param status what should be the job status - * @param why why do you want to delete this job + * @param why why do you want to delete this job */ void delete(JobStatus status, Serializable why); diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/RqueueMessageEnqueuer.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/RqueueMessageEnqueuer.java index d412fafb..a116048a 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/RqueueMessageEnqueuer.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/RqueueMessageEnqueuer.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2020-2023 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -22,13 +22,14 @@ import java.time.Instant; import java.util.Date; import java.util.concurrent.TimeUnit; +import org.springframework.messaging.converter.MessageConverter; /** * RqueueMessageEnqueuer enqueue message to Redis queue using different mechanism. Use any of the * methods from this interface to enqueue message to any queue. Queue must exist, if a queue does - * not exist then it will throw an error of the {@link - * com.github.sonus21.rqueue.exception.QueueDoesNotExist}. In such case register your queue using - * {@link RqueueEndpointManager#registerQueue(String, String...)} method. + * not exist then it will throw an error of the + * {@link com.github.sonus21.rqueue.exception.QueueDoesNotExist}. In such case register your queue + * using {@link RqueueEndpointManager#registerQueue(String, String...)} method. * *

There are four types of interfaces in this * @@ -52,7 +53,7 @@ public interface RqueueMessageEnqueuer { * Enqueue a message on given queue without any delay, consume as soon as possible. * * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. + * @param message message object it could be any arbitrary object. * @return message id on successful enqueue otherwise null. */ String enqueue(String queueName, Object message); @@ -62,7 +63,7 @@ public interface RqueueMessageEnqueuer { * * @param queueName on which queue message has to be send * @param messageId message id - * @param message message object it could be any arbitrary object. + * @param message message object it could be any arbitrary object. * @return message was enqueue successfully or failed. */ boolean enqueue(String queueName, String messageId, Object message); @@ -72,7 +73,7 @@ public interface RqueueMessageEnqueuer { * * @param queueName on which queue message has to be send * @param messageId the message id for uniqueness - * @param message message object it could be any arbitrary object. + * @param message message object it could be any arbitrary object. * @return message id on successful enqueue otherwise null. */ boolean enqueueUnique(String queueName, String messageId, Object message); @@ -81,10 +82,10 @@ public interface RqueueMessageEnqueuer { * Enqueue a message on the given queue with the given retry count. This message would not be * consumed more than the specified time due to failure in underlying systems. * - * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param message message object it could be any arbitrary object. * @param retryCount how many times a message would be retried, before it can be discarded or send - * to dead letter queue configured using {@link RqueueListener#numRetries()} + * to dead letter queue configured using {@link RqueueListener#numRetries()} * @return message id on successful enqueue otherwise null. */ String enqueueWithRetry(String queueName, Object message, int retryCount); @@ -93,11 +94,11 @@ public interface RqueueMessageEnqueuer { * Enqueue a message on the given queue with the given retry count. This message would not be * consumed more than the specified time due to failure in underlying systems. * - * @param queueName on which queue message has to be send - * @param messageId message id for this message. - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param messageId message id for this message. + * @param message message object it could be any arbitrary object. * @param retryCount how many times a message would be retried, before it can be discarded or send - * to dead letter queue configured using {@link RqueueListener#numRetries()} + * to dead letter queue configured using {@link RqueueListener#numRetries()} * @return message was enqueue successfully or failed. */ boolean enqueueWithRetry(String queueName, String messageId, Object message, int retryCount); @@ -106,8 +107,8 @@ public interface RqueueMessageEnqueuer { * Enqueue a message on given queue, that will be consumed as soon as possible. * * @param queueName on which queue message has to be send - * @param priority the priority for this message, like high, low, medium etc - * @param message message object it could be any arbitrary object. + * @param priority the priority for this message, like high, low, medium etc + * @param message message object it could be any arbitrary object. * @return message id on successful enqueue otherwise null. */ String enqueueWithPriority(String queueName, String priority, Object message); @@ -116,9 +117,9 @@ public interface RqueueMessageEnqueuer { * Enqueue a message on given queue, that will be consumed as soon as possible. * * @param queueName on which queue message has to be send - * @param priority the priority for this message, like high, low, medium etc + * @param priority the priority for this message, like high, low, medium etc * @param messageId the message id for this message - * @param message message object it could be any arbitrary object. + * @param message message object it could be any arbitrary object. * @return message was enqueued successfully or not. */ boolean enqueueWithPriority(String queueName, String priority, String messageId, Object message); @@ -127,9 +128,9 @@ public interface RqueueMessageEnqueuer { * Enqueue unique message on given queue, that will be consumed as soon as possible. * * @param queueName on which queue message has to be send - * @param priority the priority for this message, like high, low, medium etc + * @param priority the priority for this message, like high, low, medium etc * @param messageId the message id for this message - * @param message message object it could be any arbitrary object. + * @param message message object it could be any arbitrary object. * @return message was enqueue successfully or failed. */ default boolean enqueueUniqueWithPriority( @@ -142,8 +143,8 @@ default boolean enqueueUniqueWithPriority( * Schedule a message on the given queue with the provided delay. It will be available to consume * as soon as the delay elapse, for example process in 10 seconds * - * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param message message object it could be any arbitrary object. * @param delayInMilliSecs delay in milliseconds * @return message id on successful enqueue otherwise null. */ @@ -153,9 +154,9 @@ default boolean enqueueUniqueWithPriority( * Schedule a message on the given queue with the provided delay. It will be available to consume * as soon as the delay elapse. * - * @param queueName on which queue message has to be send - * @param messageId the message id, using which this message will be identified - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param messageId the message id, using which this message will be identified + * @param message message object it could be any arbitrary object. * @param delayInMilliSecs delay in milliseconds * @return message was enqueue successfully or failed. */ @@ -166,8 +167,8 @@ default boolean enqueueUniqueWithPriority( * as soon as the delay elapse. * * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. - * @param delay time to wait before it can be executed. + * @param message message object it could be any arbitrary object. + * @param delay time to wait before it can be executed. * @return message id on successful enqueue otherwise null. */ default String enqueueIn(String queueName, Object message, Duration delay) { @@ -180,8 +181,8 @@ default String enqueueIn(String queueName, Object message, Duration delay) { * * @param queueName on which queue message has to be send * @param messageId the message id, using which this message will be identified - * @param message message object it could be any arbitrary object. - * @param delay time to wait before it can be executed. + * @param message message object it could be any arbitrary object. + * @param delay time to wait before it can be executed. * @return success or failure. */ default boolean enqueueIn(String queueName, String messageId, Object message, Duration delay) { @@ -193,9 +194,9 @@ default boolean enqueueIn(String queueName, String messageId, Object message, Du * as soon as the specified delay elapse. * * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. - * @param delay time to wait before it can be executed. - * @param unit unit of the delay + * @param message message object it could be any arbitrary object. + * @param delay time to wait before it can be executed. + * @param unit unit of the delay * @return message id on successful enqueue otherwise null. */ default String enqueueIn(String queueName, Object message, long delay, TimeUnit unit) { @@ -208,9 +209,9 @@ default String enqueueIn(String queueName, Object message, long delay, TimeUnit * * @param queueName on which queue message has to be send * @param messageId message id using which this message can be identified - * @param message message object it could be any arbitrary object. - * @param delay time to wait before it can be executed. - * @param unit unit of the delay + * @param message message object it could be any arbitrary object. + * @param delay time to wait before it can be executed. + * @param unit unit of the delay * @return success or failure. */ default boolean enqueueIn( @@ -221,9 +222,9 @@ default boolean enqueueIn( /** * Enqueue a message on given queue with delay, consume as soon as the scheduled is expired. * - * @param queueName on which queue message has to be send - * @param messageId the message id for uniqueness - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param messageId the message id for uniqueness + * @param message message object it could be any arbitrary object. * @param delayInMillisecond total execution delay * @return message id on successful enqueue otherwise {@literal null}. */ @@ -233,12 +234,13 @@ boolean enqueueUniqueIn( /** * Enqueue a task that would be scheduled to run in the specified milliseconds. * - * @param queueName on which queue message has to be sent - * @param message message object it could be any arbitrary object. - * @param retryCount how many times a message would be retried, before it can be discarded or sent - * to dead letter queue configured using {@link RqueueListener#numRetries()} ()} + * @param queueName on which queue message has to be sent + * @param message message object it could be any arbitrary object. + * @param retryCount how many times a message would be retried, before it can be discarded + * or sent to dead letter queue configured using + * {@link RqueueListener#numRetries()} ()} * @param delayInMilliSecs delay in milliseconds, this message would be only visible to the - * listener when number of millisecond has elapsed. + * listener when number of millisecond has elapsed. * @return message id on successful enqueue otherwise {@literal null} */ String enqueueInWithRetry( @@ -247,13 +249,14 @@ String enqueueInWithRetry( /** * Enqueue a task that would be scheduled to run in the specified milliseconds. * - * @param queueName on which queue message has to be sent - * @param messageId the message identifier - * @param message message object it could be any arbitrary object. - * @param retryCount how many times a message would be retried, before it can be discarded or sent - * to dead letter queue configured using {@link RqueueListener#numRetries()} ()} + * @param queueName on which queue message has to be sent + * @param messageId the message identifier + * @param message message object it could be any arbitrary object. + * @param retryCount how many times a message would be retried, before it can be discarded + * or sent to dead letter queue configured using + * {@link RqueueListener#numRetries()} ()} * @param delayInMilliSecs delay in milliseconds, this message would be only visible to the - * listener when number of millisecond has elapsed. + * listener when number of millisecond has elapsed. * @return message was enqueue successfully or failed. */ boolean enqueueInWithRetry( @@ -263,9 +266,9 @@ boolean enqueueInWithRetry( * Schedule a message on the given queue at the provided time. It will be executed as soon as the * given delay is elapse. * - * @param queueName on which queue message has to be send - * @param priority the name of the priority level - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param priority the name of the priority level + * @param message message object it could be any arbitrary object. * @param delayInMilliSecs delay in milliseconds * @return message id on successful enqueue otherwise {@literal null}. */ @@ -279,10 +282,10 @@ default String enqueueInWithPriority( * Schedule a message on the given queue at the provided time. It will be executed as soon as the * given delay is elapse. * - * @param queueName on which queue message has to be send - * @param priority the name of the priority level - * @param messageId the message id - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param priority the name of the priority level + * @param messageId the message id + * @param message message object it could be any arbitrary object. * @param delayInMilliSecs delay in milliseconds * @return message was enqueue successfully or failed. */ @@ -300,9 +303,9 @@ default boolean enqueueInWithPriority( * given delay is elapse. * * @param queueName on which queue message has to be send - * @param priority the name of the priority level - * @param message message object it could be any arbitrary object. - * @param delay time to wait before it can be consumed. + * @param priority the name of the priority level + * @param message message object it could be any arbitrary object. + * @param delay time to wait before it can be consumed. * @return message id on successful enqueue otherwise {@literal null}. */ default String enqueueInWithPriority( @@ -315,10 +318,10 @@ default String enqueueInWithPriority( * given delay is elapse. * * @param queueName on which queue message has to be send - * @param priority the name of the priority level + * @param priority the name of the priority level * @param messageId the message id - * @param message message object it could be any arbitrary object. - * @param delay time to wait before it can be consumed. + * @param message message object it could be any arbitrary object. + * @param delay time to wait before it can be consumed. * @return message was enqueue successfully or failed. */ default boolean enqueueInWithPriority( @@ -331,10 +334,10 @@ default boolean enqueueInWithPriority( * given delay is elapse. * * @param queueName on which queue message has to be send - * @param priority the name of the priority level - * @param message message object it could be any arbitrary object. - * @param delay time to wait before it can be consumed. - * @param unit unit of the delay + * @param priority the name of the priority level + * @param message message object it could be any arbitrary object. + * @param delay time to wait before it can be consumed. + * @param unit unit of the delay * @return message id on a successful enqueue otherwise {@literal null}. */ default String enqueueInWithPriority( @@ -347,11 +350,11 @@ default String enqueueInWithPriority( * given delay is elapse. * * @param queueName on which queue message has to be send - * @param priority the name of the priority level + * @param priority the name of the priority level * @param messageId the message id - * @param message message object it could be any arbitrary object. - * @param delay time to wait before it can be consumed. - * @param unit unit of the delay + * @param message message object it could be any arbitrary object. + * @param delay time to wait before it can be consumed. + * @param unit unit of the delay * @return message was enqueue successfully or failed. */ default boolean enqueueInWithPriority( @@ -369,11 +372,11 @@ default boolean enqueueInWithPriority( * the given delay is elapse. * * @param queueName on which queue message has to be send - * @param priority the name of the priority level + * @param priority the name of the priority level * @param messageId the message id - * @param message message object it could be any arbitrary object. - * @param delay time to wait before it can be consumed. - * @param unit unit of the delay + * @param message message object it could be any arbitrary object. + * @param delay time to wait before it can be consumed. + * @param unit unit of the delay * @return message was enqueue successfully or failed. */ default boolean enqueueUniqueInWithPriority( @@ -394,8 +397,8 @@ default boolean enqueueUniqueInWithPriority( * Schedule a message on the given queue at the provided time. It will be available to consume as * soon as the given time is reached. * - * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param message message object it could be any arbitrary object. * @param startTimeInMilliSeconds time at which this message has to be consumed. * @return message id on successful enqueue otherwise {@literal null}. */ @@ -407,9 +410,9 @@ default String enqueueAt(String queueName, Object message, long startTimeInMilli * Schedule a message on the given queue at the provided time. It will be available to consume as * soon as the given time is reached. * - * @param queueName on which queue message has to be send - * @param messageId message id - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param messageId message id + * @param message message object it could be any arbitrary object. * @param startTimeInMilliSeconds time at which this message has to be consumed. * @return message was enqueued successfully or failed. */ @@ -424,8 +427,8 @@ default boolean enqueueAt( * soon as the given time is reached. * * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. - * @param starTime time at which this message has to be consumed. + * @param message message object it could be any arbitrary object. + * @param starTime time at which this message has to be consumed. * @return message id on successful enqueue otherwise {@literal null}. */ default String enqueueAt(String queueName, Object message, Instant starTime) { @@ -438,8 +441,8 @@ default String enqueueAt(String queueName, Object message, Instant starTime) { * * @param queueName on which queue message has to be send * @param messageId the message id - * @param message message object it could be any arbitrary object. - * @param starTime time at which this message has to be consumed. + * @param message message object it could be any arbitrary object. + * @param starTime time at which this message has to be consumed. * @return message was enqueued successfully or failed. */ default boolean enqueueAt(String queueName, String messageId, Object message, Instant starTime) { @@ -451,8 +454,8 @@ default boolean enqueueAt(String queueName, String messageId, Object message, In * soon as the given time is reached. * * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. - * @param starTime time at which this message has to be consumed. + * @param message message object it could be any arbitrary object. + * @param starTime time at which this message has to be consumed. * @return message id on successful enqueue otherwise {@literal null}. */ default String enqueueAt(String queueName, Object message, Date starTime) { @@ -465,8 +468,8 @@ default String enqueueAt(String queueName, Object message, Date starTime) { * * @param queueName on which queue message has to be send * @param messageId the message id - * @param message message object it could be any arbitrary object. - * @param starTime time at which this message has to be consumed. + * @param message message object it could be any arbitrary object. + * @param starTime time at which this message has to be consumed. * @return message was enqueued successfully or failed. */ default boolean enqueueAt(String queueName, String messageId, Object message, Date starTime) { @@ -477,9 +480,9 @@ default boolean enqueueAt(String queueName, String messageId, Object message, Da * Schedule unique messages on the given queue at the provided time. It will be available to * consume as soon as the given time is reached. * - * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. - * @param messageId a unique identifier for this message + * @param queueName on which queue message has to be send + * @param message message object it could be any arbitrary object. + * @param messageId a unique identifier for this message * @param timeInMilliSeconds time at which this message has to be consumed. * @return message was enqueue successfully or failed. */ @@ -493,9 +496,9 @@ default boolean enqueueUniqueAt( * Schedule a message on the given queue at the provided time. It will be executed as soon as the * given time is reached, time must be in the future. * - * @param queueName on which queue message has to be send - * @param priority the name of the priority level - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param priority the name of the priority level + * @param message message object it could be any arbitrary object. * @param startTimeInMilliSecond time at which the message would be consumed. * @return message id on successful enqueue otherwise {@literal null}. */ @@ -509,10 +512,10 @@ default String enqueueAtWithPriority( * Schedule a message on the given queue at the provided time. It will be executed as soon as the * given time is reached, time must be in the future. * - * @param queueName on which queue message has to be send - * @param priority the name of the priority level - * @param messageId the message id - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param priority the name of the priority level + * @param messageId the message id + * @param message message object it could be any arbitrary object. * @param startTimeInMilliSecond time at which the message would be consumed. * @return message was enqueue successfully or failed. */ @@ -535,8 +538,8 @@ default boolean enqueueAtWithPriority( * given time is reached, time must be in the future. * * @param queueName on which queue message has to be send - * @param priority the name of the priority level - * @param message message object it could be any arbitrary object. + * @param priority the name of the priority level + * @param message message object it could be any arbitrary object. * @param startTime time at which message is supposed to consume * @return message id on successful enqueue otherwise {@literal null} */ @@ -550,10 +553,10 @@ default String enqueueAtWithPriority( * given time is reached, time must be in the future. * * @param queueName on which queue message has to be send - * @param priority the name of the priority level - * @param message message object it could be any arbitrary object. + * @param priority the name of the priority level + * @param message message object it could be any arbitrary object. * @param messageId the message id - * @param instant time at which message is supposed to consume + * @param instant time at which message is supposed to consume * @return message was enqueue successfully or failed. */ default boolean enqueueAtWithPriority( @@ -566,9 +569,9 @@ default boolean enqueueAtWithPriority( * given time is reached, time must be in the future. * * @param queueName on which queue message has to be send - * @param priority the name of the priority level - * @param message message object it could be any arbitrary object. - * @param time time at which message would be consumed. + * @param priority the name of the priority level + * @param message message object it could be any arbitrary object. + * @param time time at which message would be consumed. * @return a message id on successful enqueue otherwise {@literal null} */ default String enqueueAtWithPriority( @@ -581,10 +584,10 @@ default String enqueueAtWithPriority( * given time is reached, time must be in the future. * * @param queueName on which queue message has to be send - * @param priority the name of the priority level + * @param priority the name of the priority level * @param messageId the message id - * @param message message object it could be any arbitrary object. - * @param time time at which message would be consumed. + * @param message message object it could be any arbitrary object. + * @param time time at which message would be consumed. * @return message was enqueue successfully or failed. */ default boolean enqueueAtWithPriority( @@ -596,10 +599,10 @@ default boolean enqueueAtWithPriority( * Schedule unique messages on the given queue at the provided time. It will be available to * consume as soon as the given time is reached. * - * @param queueName on which queue message has to be send - * @param priority priority of the given message - * @param message message object it could be any arbitrary object. - * @param messageId a unique identifier message id for this message + * @param queueName on which queue message has to be send + * @param priority priority of the given message + * @param message message object it could be any arbitrary object. + * @param messageId a unique identifier message id for this message * @param timeInMilliSeconds time at which this message has to be consumed. * @return message was enqueue successfully or failed. */ @@ -622,8 +625,8 @@ default boolean enqueueUniqueAtWithPriority( * Enqueue a message on given queue that will be running after a given period. It works like * periodic cron that's scheduled at certain interval, for example every 30 seconds. * - * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param message message object it could be any arbitrary object. * @param periodInMilliSeconds period of this job in milliseconds. * @return message id on successful enqueue otherwise null. */ @@ -634,9 +637,9 @@ default boolean enqueueUniqueAtWithPriority( * periodic cron that's scheduled at certain interval, for example every 30 seconds. * * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. - * @param period period of this job - * @param unit period unit + * @param message message object it could be any arbitrary object. + * @param period period of this job + * @param unit period unit * @return message id on successful enqueue otherwise null. */ default String enqueuePeriodic(String queueName, Object message, long period, TimeUnit unit) { @@ -648,8 +651,8 @@ default String enqueuePeriodic(String queueName, Object message, long period, Ti * periodic cron that's scheduled at certain interval, for example every 30 seconds. * * @param queueName on which queue message has to be send - * @param message message object it could be any arbitrary object. - * @param period job period + * @param message message object it could be any arbitrary object. + * @param period job period * @return message id on successful enqueue otherwise null. */ default String enqueuePeriodic(String queueName, Object message, Duration period) { @@ -660,9 +663,9 @@ default String enqueuePeriodic(String queueName, Object message, Duration period * Enqueue a message on given queue that will be running after a given period. It works like * periodic cron that's scheduled at certain interval, for example every 30 seconds. * - * @param queueName on which queue message has to be send - * @param messageId message id corresponding to this message - * @param message message object it could be any arbitrary object. + * @param queueName on which queue message has to be send + * @param messageId message id corresponding to this message + * @param message message object it could be any arbitrary object. * @param periodInMilliSeconds period of this job in milliseconds. * @return success or failure */ @@ -675,9 +678,9 @@ boolean enqueuePeriodic( * * @param queueName on which queue message has to be send * @param messageId message id corresponding to this message - * @param message message object it could be any arbitrary object. - * @param period period of this job . - * @param unit unit of this period + * @param message message object it could be any arbitrary object. + * @param period period of this job . + * @param unit unit of this period * @return success or failure */ default boolean enqueuePeriodic( @@ -691,12 +694,14 @@ default boolean enqueuePeriodic( * * @param queueName on which queue message has to be send * @param messageId message id corresponding to this message - * @param message message object it could be any arbitrary object. - * @param period period of this job . + * @param message message object it could be any arbitrary object. + * @param period period of this job . * @return success or failure */ default boolean enqueuePeriodic( String queueName, String messageId, Object message, Duration period) { return enqueuePeriodic(queueName, messageId, message, period.toMillis()); } + + MessageConverter getMessageConverter(); } diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/RqueueMessageManager.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/RqueueMessageManager.java index 62b411a8..009642ac 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/RqueueMessageManager.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/RqueueMessageManager.java @@ -1,22 +1,23 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2020-2023 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ package com.github.sonus21.rqueue.core; import com.github.sonus21.rqueue.config.RqueueConfig; +import com.github.sonus21.rqueue.utils.Constants; import com.github.sonus21.rqueue.utils.PriorityUtils; import java.util.List; import org.springframework.messaging.converter.MessageConverter; @@ -43,7 +44,7 @@ public interface RqueueMessageManager { * Delete all message for the given that has some priority like high,medium and low * * @param queueName queue name - * @param priority the priority for the queue + * @param priority the priority for the queue * @return fail/success */ default boolean deleteAllMessages(String queueName, String priority) { @@ -65,7 +66,7 @@ default boolean deleteAllMessages(String queueName, String priority) { * method {@link #getAllMessages(String)} * * @param queueName queue name to be query for - * @param priority the priority of the queue + * @param priority the priority of the queue * @return list of enqueued messages. */ default List getAllMessages(String queueName, String priority) { @@ -77,7 +78,7 @@ default List getAllMessages(String queueName, String priority) { * consumption message has a fixed lifetime. * * @param queueName queue name on which message was enqueued - * @param id message id + * @param id message id * @return the enqueued message, it could be null if message is not found or it's deleted. * @see RqueueConfig */ @@ -88,8 +89,8 @@ default List getAllMessages(String queueName, String priority) { * priority queue. * * @param queueName queue name on which message was enqueued - * @param priority the priority of the queue - * @param id message id + * @param priority the priority of the queue + * @param id message id * @return the enqueued message, it could be null if message is not found or it's deleted. */ default Object getMessage(String queueName, String priority, String id) { @@ -101,17 +102,18 @@ default Object getMessage(String queueName, String priority, String id) { * returns true/false. * * @param queueName queue name on which message was enqueued - * @param id message id + * @param id message id * @return whether the message exist or not */ boolean exist(String queueName, String id); /** - * Extension to the method {@link #exist(String, String)}, that checks message for priority queue. + * Extension to the method {@link #exist(String, String)}, that checks message for priority + * queue. * * @param queueName queue name on which message was enqueued - * @param priority priority of the given queue - * @param id message id + * @param priority priority of the given queue + * @param id message id * @return whether the message exist or not */ default boolean exist(String queueName, String priority, String id) { @@ -122,7 +124,7 @@ default boolean exist(String queueName, String priority, String id) { * Extension to the method {@link #getMessage(String, String)}, this returns internal message. * * @param queueName queue name on which message was enqueued - * @param id message id + * @param id message id * @return the enqueued message */ RqueueMessage getRqueueMessage(String queueName, String id); @@ -131,8 +133,8 @@ default boolean exist(String queueName, String priority, String id) { * Extension to the method {@link #getRqueueMessage(String, String)} * * @param queueName queue name on which message was enqueued - * @param priority the priority of the queue - * @param id message id + * @param priority the priority of the queue + * @param id message id * @return the enqueued message */ default RqueueMessage getRqueueMessage(String queueName, String priority, String id) { @@ -151,7 +153,7 @@ default RqueueMessage getRqueueMessage(String queueName, String priority, String * Extension to the method {@link #getAllRqueueMessage(String)} * * @param queueName queue name on which message was enqueued - * @param priority the priority of the queue + * @param priority the priority of the queue * @return the enqueued message */ default List getAllRqueueMessage(String queueName, String priority) { @@ -171,7 +173,7 @@ default List getAllRqueueMessage(String queueName, String priorit * Delete a message that's enqueued to a queue with some priority * * @param queueName queue on which message was enqueued - * @param priority priority of the message like high/low/medium + * @param priority priority of the message like high/low/medium * @param messageId messageId corresponding to this message * @return success/failure */ @@ -185,4 +187,27 @@ default boolean deleteMessage(String queueName, String priority, String messageI * @return message converter that's used for message (de)serialization */ MessageConverter getMessageConverter(); + + /** + * Move messages from Dead Letter queue to the destination queue. This push the messages at the + * FRONT of destination queue, so that it can be reprocessed as soon as possible. + * + * @param deadLetterQueueName dead letter queue name + * @param queueName queue name + * @param maxMessages number of messages to be moved by default move + * {@link Constants#MAX_MESSAGES} messages + * @return success or failure. + */ + boolean moveMessageFromDeadLetterToQueue( + String deadLetterQueueName, String queueName, Integer maxMessages); + + /** + * A shortcut to the method {@link #moveMessageFromDeadLetterToQueue(String, String, Integer)} + * + * @param deadLetterQueueName dead letter queue name + * @param queueName queue name + * @return success or failure + */ + boolean moveMessageFromDeadLetterToQueue(String deadLetterQueueName, String queueName); + } diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/BaseMessageSender.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/BaseMessageSender.java index 860da4b5..3002a204 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/BaseMessageSender.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/BaseMessageSender.java @@ -1,16 +1,16 @@ /* - * Copyright 2022 Sonu Kumar + * Copyright (c) 2020-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -29,6 +29,7 @@ import com.github.sonus21.rqueue.core.RqueueMessageTemplate; import com.github.sonus21.rqueue.core.impl.MessageSweeper.MessageDeleteRequest; import com.github.sonus21.rqueue.dao.RqueueStringDao; +import com.github.sonus21.rqueue.exception.DuplicateMessageException; import com.github.sonus21.rqueue.listener.QueueDetail; import com.github.sonus21.rqueue.models.db.MessageMetadata; import com.github.sonus21.rqueue.models.enums.MessageStatus; @@ -66,13 +67,13 @@ abstract class BaseMessageSender { } protected Object storeMessageMetadata( - RqueueMessage rqueueMessage, Long delayInMillis, boolean reactive) { + RqueueMessage rqueueMessage, Long delayInMillis, boolean reactive, boolean isUnique) { MessageMetadata messageMetadata = new MessageMetadata(rqueueMessage, MessageStatus.ENQUEUED); Duration duration = rqueueConfig.getMessageDurability(delayInMillis); if (reactive) { - return rqueueMessageMetadataService.saveReactive(messageMetadata, duration); + return rqueueMessageMetadataService.saveReactive(messageMetadata, duration, isUnique); } else { - rqueueMessageMetadataService.save(messageMetadata, duration); + rqueueMessageMetadataService.save(messageMetadata, duration, isUnique); } return null; } @@ -109,7 +110,8 @@ protected String pushMessage( String messageId, Object message, Integer retryCount, - Long delayInMilliSecs) { + Long delayInMilliSecs, + boolean isUnique) { QueueDetail queueDetail = EndpointRegistry.get(queueName); RqueueMessage rqueueMessage = buildMessage( @@ -121,17 +123,26 @@ protected String pushMessage( delayInMilliSecs, messageHeaders); try { + storeMessageMetadata(rqueueMessage, delayInMilliSecs, false, isUnique); enqueue(queueDetail, rqueueMessage, delayInMilliSecs, false); - storeMessageMetadata(rqueueMessage, delayInMilliSecs, false); + } catch (DuplicateMessageException e) { + log.warn( + "Duplicate message enqueue attempted queue: {}, messageId: {}", + queueName, + rqueueMessage.getId()); + return null; } catch (Exception e) { - log.error("Queue: {} Message {} could not be pushed {}", queueName, rqueueMessage, e); + log.error("Queue: {} Message {} could not be pushed", queueName, rqueueMessage.getId(), e); return null; } return rqueueMessage.getId(); } protected String pushPeriodicMessage( - String queueName, String messageId, Object message, long periodInMilliSeconds) { + String queueName, + String messageId, + Object message, + long periodInMilliSeconds) { QueueDetail queueDetail = EndpointRegistry.get(queueName); RqueueMessage rqueueMessage = buildPeriodicMessage( @@ -143,13 +154,13 @@ protected String pushPeriodicMessage( periodInMilliSeconds, messageHeaders); try { + storeMessageMetadata(rqueueMessage, periodInMilliSeconds, false, false); enqueue(queueDetail, rqueueMessage, periodInMilliSeconds, false); - storeMessageMetadata(rqueueMessage, periodInMilliSeconds, false); + return rqueueMessage.getId(); } catch (Exception e) { - log.error("Queue: {} Message {} could not be pushed {}", queueName, rqueueMessage, e); + log.error("Queue: {} Message {} could not be pushed", queueName, rqueueMessage, e); return null; } - return rqueueMessage.getId(); } protected Object deleteAllMessages(QueueDetail queueDetail) { diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/ReactiveRqueueMessageEnqueuerImpl.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/ReactiveRqueueMessageEnqueuerImpl.java index b1f2bb20..bf752c0b 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/ReactiveRqueueMessageEnqueuerImpl.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/ReactiveRqueueMessageEnqueuerImpl.java @@ -1,34 +1,29 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2021-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ package com.github.sonus21.rqueue.core.impl; -import static com.github.sonus21.rqueue.utils.Validator.validateDelay; -import static com.github.sonus21.rqueue.utils.Validator.validateMessage; -import static com.github.sonus21.rqueue.utils.Validator.validateMessageId; -import static com.github.sonus21.rqueue.utils.Validator.validatePeriod; -import static com.github.sonus21.rqueue.utils.Validator.validatePriority; -import static com.github.sonus21.rqueue.utils.Validator.validateQueue; -import static com.github.sonus21.rqueue.utils.Validator.validateRetryCount; +import static com.github.sonus21.rqueue.utils.Validator.*; import com.github.sonus21.rqueue.core.EndpointRegistry; import com.github.sonus21.rqueue.core.ReactiveRqueueMessageEnqueuer; import com.github.sonus21.rqueue.core.RqueueMessage; import com.github.sonus21.rqueue.core.RqueueMessageTemplate; import com.github.sonus21.rqueue.core.support.RqueueMessageUtils; +import com.github.sonus21.rqueue.exception.DuplicateMessageException; import com.github.sonus21.rqueue.listener.QueueDetail; import com.github.sonus21.rqueue.utils.PriorityUtils; import lombok.extern.slf4j.Slf4j; @@ -37,6 +32,9 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.function.BiFunction; +import java.util.function.Function; + @Slf4j public class ReactiveRqueueMessageEnqueuerImpl extends BaseMessageSender implements ReactiveRqueueMessageEnqueuer { @@ -56,7 +54,8 @@ private Mono pushReactiveMessage( Object message, Integer retryCount, Long delayInMilliSecs, - MonoConverterGenerator monoConverterGenerator) { + boolean isUnique, + Function> monoConverter) { QueueDetail queueDetail = EndpointRegistry.get(queueName); RqueueMessage rqueueMessage = builder.build( @@ -67,24 +66,46 @@ private Mono pushReactiveMessage( retryCount, delayInMilliSecs, messageHeaders); - MonoConverter monoConverter = monoConverterGenerator.create(rqueueMessage); try { - Object o1 = enqueue(queueDetail, rqueueMessage, delayInMilliSecs, true); - Mono storeMessageResult = - (Mono) storeMessageMetadata(rqueueMessage, delayInMilliSecs, true); - Mono longMono; - if (o1 instanceof Flux) { - longMono = ((Flux) o1).elementAt(0); - } else { - longMono = (Mono) o1; - } - return longMono.zipWith(storeMessageResult, monoConverter::call); + Mono storeResult = + (Mono) storeMessageMetadata(rqueueMessage, delayInMilliSecs, true, isUnique); + return storeResult.flatMap( + success -> { + if (Boolean.TRUE.equals(success)) { + Object result = enqueue(queueDetail, rqueueMessage, delayInMilliSecs, true); + Mono enqueueMono; + if (result instanceof Flux) { + enqueueMono = ((Flux) result).next(); + } else if (result instanceof Mono) { + enqueueMono = (Mono) result; + } else { + return Mono.error( + new IllegalStateException( + "Unexpected enqueue result type: " + result.getClass())); + } + return enqueueMono.flatMap(ignore -> monoConverter.apply(rqueueMessage)); + } else { + return Mono.error(new DuplicateMessageException(rqueueMessage.getId())); + } + }); } catch (Exception e) { - log.error("Queue: {} Message {} could not be pushed {}", queueName, rqueueMessage, e); + log.error( + "Failed to enqueue message [{}] to queue [{}]", rqueueMessage.getId(), queueName, e); return Mono.error(e); } } + private void validateBasic(String queue, Object msg) { + validateQueue(queue); + validateMessage(msg); + } + + private void validateWithId(String queue, String id, Object msg) { + validateQueue(queue); + validateMessageId(id); + validateMessage(msg); + } + private Mono pushReactiveMessage( String queueName, Object message, Integer retryCount, Long delayInMilliSecs) { return pushReactiveMessage( @@ -94,7 +115,8 @@ private Mono pushReactiveMessage( message, retryCount, delayInMilliSecs, - new StrMonoConverterGenerator()); + false, + (rqueueMessage) -> Mono.just(rqueueMessage.getId())); } private Mono pushReactiveWithMessageId( @@ -102,7 +124,8 @@ private Mono pushReactiveWithMessageId( String messageId, Object message, Integer retryCount, - Long delayInMilliSecs) { + Long delayInMilliSecs, + boolean isUnique) { return pushReactiveMessage( RqueueMessageUtils::buildMessage, queueName, @@ -110,7 +133,8 @@ private Mono pushReactiveWithMessageId( message, retryCount, delayInMilliSecs, - new BooleanMonoConverterGenerator()); + isUnique, + (rqueueMessage) -> Mono.just(Boolean.TRUE)); } private Mono pushReactivePeriodicMessage( @@ -122,7 +146,8 @@ private Mono pushReactivePeriodicMessage( message, null, periodInMilliSeconds, - new StrMonoConverterGenerator()); + false, + (rqueueMessage) -> Mono.just(rqueueMessage.getId())); } private Mono pushReactivePeriodicMessageWithMessageId( @@ -134,37 +159,31 @@ private Mono pushReactivePeriodicMessageWithMessageId( message, null, periodInMilliSeconds, - new BooleanMonoConverterGenerator()); + false, + (rqueueMessage) -> Mono.just(Boolean.TRUE)); } @Override public Mono enqueue(String queueName, Object message) { - validateQueue(queueName); - validateMessage(message); + validateBasic(queueName, message); return pushReactiveMessage(queueName, message, null, null); } @Override public Mono enqueue(String queueName, String messageId, Object message) { - validateQueue(queueName); - validateMessageId(messageId); - validateMessage(message); - return pushReactiveWithMessageId(queueName, messageId, message, null, null); + validateWithId(queueName, messageId, message); + return pushReactiveWithMessageId(queueName, messageId, message, null, null, false); } @Override public Mono enqueueUnique(String queueName, String messageId, Object message) { - validateQueue(queueName); - validateMessageId(messageId); - validateMessage(message); - // TODO uniqueness - return pushReactiveWithMessageId(queueName, messageId, message, null, null); + validateWithId(queueName, messageId, message); + return pushReactiveWithMessageId(queueName, messageId, message, null, null, true); } @Override public Mono enqueueWithRetry(String queueName, Object message, int retryCount) { - validateQueue(queueName); - validateMessage(message); + validateBasic(queueName, message); validateRetryCount(retryCount); return pushReactiveMessage(queueName, message, retryCount, null); } @@ -172,11 +191,9 @@ public Mono enqueueWithRetry(String queueName, Object message, int retry @Override public Mono enqueueWithRetry( String queueName, String messageId, Object message, int retryCount) { - validateQueue(queueName); - validateMessageId(messageId); - validateMessage(message); + validateWithId(queueName, messageId, message); validateRetryCount(retryCount); - return pushReactiveWithMessageId(queueName, messageId, message, retryCount, null); + return pushReactiveWithMessageId(queueName, messageId, message, retryCount, null, false); } @Override @@ -191,18 +208,20 @@ public Mono enqueueWithPriority(String queueName, String priority, Objec @Override public Mono enqueueWithPriority( String queueName, String priority, String messageId, Object message) { - validateQueue(queueName); + validateWithId(queueName, messageId, message); validatePriority(priority); - validateMessageId(messageId); - validateMessage(message); return pushReactiveWithMessageId( - PriorityUtils.getQueueNameForPriority(queueName, priority), messageId, message, null, null); + PriorityUtils.getQueueNameForPriority(queueName, priority), + messageId, + message, + null, + null, + false); } @Override public Mono enqueueIn(String queueName, Object message, long delayInMilliSecs) { - validateQueue(queueName); - validateMessage(message); + validateBasic(queueName, message); validateDelay(delayInMilliSecs); return pushReactiveMessage(queueName, message, null, delayInMilliSecs); } @@ -210,29 +229,23 @@ public Mono enqueueIn(String queueName, Object message, long delayInMill @Override public Mono enqueueIn( String queueName, String messageId, Object message, long delayInMilliSecs) { - validateQueue(queueName); - validateMessageId(messageId); - validateMessage(message); + validateWithId(queueName, messageId, message); validateDelay(delayInMilliSecs); - return pushReactiveWithMessageId(queueName, messageId, message, null, delayInMilliSecs); + return pushReactiveWithMessageId(queueName, messageId, message, null, delayInMilliSecs, false); } @Override public Mono enqueueUniqueIn( String queueName, String messageId, Object message, long delayInMillisecond) { - validateQueue(queueName); - validateMessageId(messageId); - validateMessage(message); + validateWithId(queueName, messageId, message); validateDelay(delayInMillisecond); - // TODO unique?? - return pushReactiveWithMessageId(queueName, messageId, message, null, delayInMillisecond); + return pushReactiveWithMessageId(queueName, messageId, message, null, delayInMillisecond, true); } @Override public Mono enqueueInWithRetry( String queueName, Object message, int retryCount, long delayInMilliSecs) { - validateQueue(queueName); - validateMessage(message); + validateBasic(queueName, message); validateRetryCount(retryCount); validateDelay(delayInMilliSecs); return pushReactiveMessage(queueName, message, retryCount, delayInMilliSecs); @@ -241,18 +254,16 @@ public Mono enqueueInWithRetry( @Override public Mono enqueueInWithRetry( String queueName, String messageId, Object message, int retryCount, long delayInMilliSecs) { - validateQueue(queueName); - validateMessageId(messageId); - validateMessage(message); - validateDelay(retryCount); + validateWithId(queueName, messageId, message); + validateRetryCount(retryCount); validateDelay(delayInMilliSecs); - return pushReactiveWithMessageId(queueName, messageId, message, retryCount, delayInMilliSecs); + return pushReactiveWithMessageId( + queueName, messageId, message, retryCount, delayInMilliSecs, false); } @Override public Mono enqueuePeriodic(String queueName, Object message, long periodInMilliSeconds) { - validateQueue(queueName); - validateMessage(message); + validateBasic(queueName, message); validatePeriod(periodInMilliSeconds); return pushReactivePeriodicMessage(queueName, message, periodInMilliSeconds); } @@ -260,21 +271,19 @@ public Mono enqueuePeriodic(String queueName, Object message, long perio @Override public Mono enqueuePeriodic( String queueName, String messageId, Object message, long periodInMilliSeconds) { - validateQueue(queueName); - validateMessage(message); - validateMessageId(messageId); + validateWithId(queueName, messageId, message); validatePeriod(periodInMilliSeconds); return pushReactivePeriodicMessageWithMessageId( queueName, messageId, message, periodInMilliSeconds); } + @FunctionalInterface private interface MonoConverter { - - T call(Long a, Boolean b); + T convert(Long id, Boolean success); } + @FunctionalInterface private interface MessageBuilder { - RqueueMessage build( MessageConverter converter, String queueName, @@ -284,49 +293,4 @@ RqueueMessage build( Long delay, MessageHeaders messageHeaders); } - - private interface MonoConverterGenerator { - - MonoConverter create(RqueueMessage rqueueMessage); - } - - private static class StrMonoConverter implements MonoConverter { - - private final RqueueMessage message; - - private StrMonoConverter(RqueueMessage message) { - this.message = message; - } - - @Override - public String call(Long a, Boolean b) { - return message.getId(); - } - } - - private static class BoolMonoConverter implements MonoConverter { - - private BoolMonoConverter(RqueueMessage message) {} - - @Override - public Boolean call(Long a, Boolean b) { - return Boolean.TRUE; - } - } - - private static class StrMonoConverterGenerator implements MonoConverterGenerator { - - @Override - public MonoConverter create(RqueueMessage rqueueMessage) { - return new StrMonoConverter(rqueueMessage); - } - } - - private static class BooleanMonoConverterGenerator implements MonoConverterGenerator { - - @Override - public MonoConverter create(RqueueMessage rqueueMessage) { - return new BoolMonoConverter(rqueueMessage); - } - } } diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageEnqueuerImpl.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageEnqueuerImpl.java index c333f7e0..e4f4fc04 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageEnqueuerImpl.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageEnqueuerImpl.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2020-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -31,6 +31,8 @@ import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.MessageConverter; +import java.util.Objects; + @Slf4j public class RqueueMessageEnqueuerImpl extends BaseMessageSender implements RqueueMessageEnqueuer { @@ -41,129 +43,136 @@ public RqueueMessageEnqueuerImpl( super(messageTemplate, messageConverter, messageHeaders); } + private void validateBasic(String queue, Object message) { + validateQueue(queue); + validateMessage(message); + } + + private void validateWithId(String queue, String messageId, Object message) { + validateQueue(queue); + validateMessageId(messageId); + validateMessage(message); + } + @Override public String enqueue(String queueName, Object message) { - validateQueue(queueName); - validateMessage(message); - return pushMessage(queueName, null, message, null, null); + validateBasic(queueName, message); + return pushMessage(queueName, null, message, null, null, false); } @Override public boolean enqueue(String queueName, String messageId, Object message) { - validateQueue(queueName); - validateMessageId(messageId); - validateMessage(message); - return pushMessage(queueName, messageId, message, null, null) != null; + validateWithId(queueName, messageId, message); + return pushMessage(queueName, messageId, message, null, null, false) != null; } @Override public boolean enqueueUnique(String queueName, String messageId, Object message) { - // TODO? is using monotonic time sufficient for handling uniqueness - return enqueue(queueName, messageId, message); + validateWithId(queueName, messageId, message); + return Objects.nonNull(pushMessage(queueName, messageId, message, null, null, true)); } @Override public String enqueueWithRetry(String queueName, Object message, int retryCount) { - validateQueue(queueName); - validateMessage(message); + validateBasic(queueName, message); validateRetryCount(retryCount); - return pushMessage(queueName, null, message, retryCount, null); + return pushMessage(queueName, null, message, retryCount, null, false); } @Override public boolean enqueueWithRetry( String queueName, String messageId, Object message, int retryCount) { - validateQueue(queueName); - validateMessageId(messageId); - validateMessage(message); + validateWithId(queueName, messageId, message); validateRetryCount(retryCount); - return pushMessage(queueName, messageId, message, retryCount, null) != null; + return pushMessage(queueName, messageId, message, retryCount, null, false) != null; } @Override public String enqueueWithPriority(String queueName, String priority, Object message) { - validateQueue(queueName); + validateBasic(queueName, message); validatePriority(priority); - validateMessage(message); return pushMessage( - PriorityUtils.getQueueNameForPriority(queueName, priority), null, message, null, null); + PriorityUtils.getQueueNameForPriority(queueName, priority), + null, + message, + null, + null, + false); } @Override public boolean enqueueWithPriority( String queueName, String priority, String messageId, Object message) { - validateQueue(queueName); + validateWithId(queueName, messageId, message); validatePriority(priority); - validateMessageId(messageId); - validateMessage(message); return pushMessage( PriorityUtils.getQueueNameForPriority(queueName, priority), messageId, message, null, - null) + null, + false) != null; } @Override public String enqueueIn(String queueName, Object message, long delayInMilliSecs) { - validateQueue(queueName); - validateMessage(message); + validateBasic(queueName, message); validateDelay(delayInMilliSecs); - return pushMessage(queueName, null, message, null, delayInMilliSecs); + return pushMessage(queueName, null, message, null, delayInMilliSecs, false); } @Override public boolean enqueueIn( String queueName, String messageId, Object message, long delayInMilliSecs) { - validateQueue(queueName); - validateMessageId(messageId); - validateMessage(message); + validateWithId(queueName, messageId, message); validateDelay(delayInMilliSecs); - return pushMessage(queueName, messageId, message, null, delayInMilliSecs) != null; + return pushMessage(queueName, messageId, message, null, delayInMilliSecs, false) != null; } @Override public boolean enqueueUniqueIn( - String queueName, String messageId, Object message, long delayInMillisecond) { - return enqueueIn(queueName, messageId, message, delayInMillisecond); + String queueName, String messageId, Object message, long delayInMilliSecs) { + validateWithId(queueName, messageId, message); + validateDelay(delayInMilliSecs); + return Objects.nonNull( + pushMessage(queueName, messageId, message, null, delayInMilliSecs, true)); } @Override public String enqueueInWithRetry( String queueName, Object message, int retryCount, long delayInMilliSecs) { - validateQueue(queueName); - validateMessage(message); + validateBasic(queueName, message); validateRetryCount(retryCount); validateDelay(delayInMilliSecs); - return pushMessage(queueName, null, message, retryCount, delayInMilliSecs); + return pushMessage(queueName, null, message, retryCount, delayInMilliSecs, false); } @Override public boolean enqueueInWithRetry( String queueName, String messageId, Object message, int retryCount, long delayInMilliSecs) { - validateQueue(queueName); - validateMessageId(messageId); - validateMessage(message); + validateWithId(queueName, messageId, message); validateRetryCount(retryCount); validateDelay(delayInMilliSecs); - return pushMessage(queueName, messageId, message, retryCount, delayInMilliSecs) != null; + return pushMessage(queueName, messageId, message, retryCount, delayInMilliSecs, false) != null; } @Override public String enqueuePeriodic(String queueName, Object message, long period) { - validateQueue(queueName); - validateMessage(message); + validateBasic(queueName, message); validatePeriod(period); return pushPeriodicMessage(queueName, null, message, period); } @Override public boolean enqueuePeriodic(String queueName, String messageId, Object message, long period) { - validateMessageId(messageId); - validateQueue(queueName); - validateMessage(message); + validateWithId(queueName, messageId, message); validatePeriod(period); return pushPeriodicMessage(queueName, messageId, message, period) != null; } + + @Override + public MessageConverter getMessageConverter() { + return messageConverter; + } } diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageManagerImpl.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageManagerImpl.java index 7d799547..9e63256b 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageManagerImpl.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageManagerImpl.java @@ -1,21 +1,24 @@ /* - * Copyright 2022 Sonu Kumar + * Copyright (c) 2020-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ package com.github.sonus21.rqueue.core.impl; +import static org.springframework.util.Assert.isTrue; +import static org.springframework.util.Assert.notNull; + import com.github.sonus21.rqueue.common.RqueueLockManager; import com.github.sonus21.rqueue.core.EndpointRegistry; import com.github.sonus21.rqueue.core.RqueueMessage; @@ -25,7 +28,13 @@ import com.github.sonus21.rqueue.exception.LockCanNotBeAcquired; import com.github.sonus21.rqueue.listener.QueueDetail; import com.github.sonus21.rqueue.listener.RqueueMessageHeaders; +import com.github.sonus21.rqueue.models.MessageMoveResult; import com.github.sonus21.rqueue.models.db.MessageMetadata; +import com.github.sonus21.rqueue.utils.Constants; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; @@ -33,11 +42,6 @@ import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.support.MessageBuilder; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - @Slf4j public class RqueueMessageManagerImpl extends BaseMessageSender implements RqueueMessageManager { @@ -104,19 +108,14 @@ public List getAllRqueueMessage(String queueName) { @Override public boolean exist(String queueName, String id) { - String lockValue = UUID.randomUUID().toString(); - if (rqueueLockManager.acquireLock(queueName, lockValue, Duration.ofSeconds(1))) { - boolean exist = getMessage(queueName, id) != null; - rqueueLockManager.releaseLock(queueName, lockValue); - return exist; - } - throw new LockCanNotBeAcquired(queueName); + return getMessage(queueName, id) != null; } @Override public boolean deleteMessage(String queueName, String id) { RqueueMessage rqueueMessage = getRqueueMessage(queueName, id); if (rqueueMessage == null) { + log.error("Delete message failed, no such message: {}", id); return false; } Duration duration = rqueueConfig.getMessageDurability(rqueueMessage.getPeriod()); @@ -127,4 +126,30 @@ public boolean deleteMessage(String queueName, String id) { public MessageConverter getMessageConverter() { return messageConverter; } + + @Override + public boolean moveMessageFromDeadLetterToQueue( + String deadLetterQueueName, String queueName, Integer maxMessages) { + return moveMessageListToList(deadLetterQueueName, queueName, maxMessages).isSuccess(); + } + + @Override + public boolean moveMessageFromDeadLetterToQueue(String deadLetterQueueName, String queueName) { + return moveMessageListToList(deadLetterQueueName, queueName, null).isSuccess(); + } + + private MessageMoveResult moveMessageListToList( + String sourceQueue, String destinationQueue, Integer maxMessage) { + notNull(sourceQueue, "sourceQueue must not be null"); + notNull(destinationQueue, "destinationQueue must not be null"); + isTrue( + !sourceQueue.equals(destinationQueue), + "sourceQueue and destinationQueue must be different"); + Integer messageCount = maxMessage; + if (messageCount == null) { + messageCount = Constants.MAX_MESSAGES; + } + isTrue(messageCount > 0, "maxMessage must be greater than zero"); + return messageTemplate.moveMessageListToList(sourceQueue, destinationQueue, messageCount); + } } diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageSenderImpl.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageSenderImpl.java index 9a30d5f4..f0afb4a8 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageSenderImpl.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/core/impl/RqueueMessageSenderImpl.java @@ -54,7 +54,7 @@ public RqueueMessageSenderImpl( public boolean enqueue(String queueName, Object message) { validateQueue(queueName); validateMessage(message); - return pushMessage(queueName, null, message, null, null) != null; + return pushMessage(queueName, null, message, null, null, false) != null; } @Override @@ -62,7 +62,7 @@ public boolean enqueueWithRetry(String queueName, Object message, int retryCount validateQueue(queueName); validateMessage(message); validateRetryCount(retryCount); - return pushMessage(queueName, null, message, retryCount, null) != null; + return pushMessage(queueName, null, message, retryCount, null, false) != null; } @Override @@ -71,7 +71,7 @@ public boolean enqueueWithPriority(String queueName, String priority, Object mes validatePriority(priority); validateMessage(message); return pushMessage( - PriorityUtils.getQueueNameForPriority(queueName, priority), null, message, null, null) + PriorityUtils.getQueueNameForPriority(queueName, priority), null, message, null, null, false) != null; } @@ -80,7 +80,7 @@ public boolean enqueueIn(String queueName, Object message, long delayInMilliSecs validateQueue(queueName); validateMessage(message); validateDelay(delayInMilliSecs); - return pushMessage(queueName, null, message, null, delayInMilliSecs) != null; + return pushMessage(queueName, null, message, null, delayInMilliSecs, false) != null; } @Override @@ -90,7 +90,7 @@ public boolean enqueueInWithRetry( validateMessage(message); validateRetryCount(retryCount); validateDelay(delayInMilliSecs); - return pushMessage(queueName, null, message, retryCount, delayInMilliSecs) != null; + return pushMessage(queueName, null, message, retryCount, delayInMilliSecs, false) != null; } @Override @@ -105,7 +105,8 @@ public boolean enqueueInWithPriority( null, message, null, - delayInMilliSecs) + delayInMilliSecs, + false) != null; } diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/dao/RqueueMessageMetadataDao.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/dao/RqueueMessageMetadataDao.java index c59bb8d7..05be7034 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/dao/RqueueMessageMetadataDao.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/dao/RqueueMessageMetadataDao.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2019-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -28,11 +28,11 @@ public interface RqueueMessageMetadataDao { List findAll(Collection ids); - void save(MessageMetadata messageMetadata, Duration ttl); + void save(MessageMetadata messageMetadata, Duration ttl, boolean checkUniqueNess); void delete(String id); void deleteAll(Collection ids); - Mono saveReactive(MessageMetadata messageMetadata, Duration duration); + Mono saveReactive(MessageMetadata messageMetadata, Duration duration, boolean isUnique); } diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/dao/impl/RqueueMessageMetadataDaoImpl.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/dao/impl/RqueueMessageMetadataDaoImpl.java index 7e99408b..c045b9f1 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/dao/impl/RqueueMessageMetadataDaoImpl.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/dao/impl/RqueueMessageMetadataDaoImpl.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2021-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -20,6 +20,7 @@ import com.github.sonus21.rqueue.common.RqueueRedisTemplate; import com.github.sonus21.rqueue.config.RqueueConfig; import com.github.sonus21.rqueue.dao.RqueueMessageMetadataDao; +import com.github.sonus21.rqueue.exception.DuplicateMessageException; import com.github.sonus21.rqueue.models.db.MessageMetadata; import java.time.Duration; import java.util.Collection; @@ -57,8 +58,15 @@ public List findAll(Collection ids) { } @Override - public void save(MessageMetadata messageMetadata, Duration duration) { + public void save(MessageMetadata messageMetadata, Duration duration,boolean checkUniqueNess) { Assert.notNull(messageMetadata.getId(), "messageMetadata id cannot be null"); + if(checkUniqueNess){ + Boolean value = template.setIfAbsent(messageMetadata.getId(), messageMetadata, duration); + if(Boolean.FALSE.equals(value)){ + throw new DuplicateMessageException(messageMetadata.getId()); + } + return; + } template.set(messageMetadata.getId(), messageMetadata, duration); } @@ -73,8 +81,19 @@ public void deleteAll(Collection ids) { } @Override - public Mono saveReactive(MessageMetadata messageMetadata, Duration ttl) { + public Mono saveReactive(MessageMetadata messageMetadata, Duration ttl, boolean isUnique) { Assert.notNull(messageMetadata.getId(), "messageMetadata id cannot be null"); + if(isUnique){ + return reactiveRedisTemplate.template().opsForValue() + .setIfAbsent(messageMetadata.getId(), messageMetadata) + .flatMap(result -> { + if (Boolean.TRUE.equals(result)) { + return reactiveRedisTemplate.template().expire(messageMetadata.getId(), ttl) + .thenReturn(true); + } + return Mono.just(false); + }); + } return reactiveRedisTemplate .template() .opsForValue() diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/exception/DuplicateMessageException.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/exception/DuplicateMessageException.java new file mode 100644 index 00000000..9aa4acf5 --- /dev/null +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/exception/DuplicateMessageException.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015-2025 Sonu Kumar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +package com.github.sonus21.rqueue.exception; + +public class DuplicateMessageException extends RuntimeException { + + public DuplicateMessageException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/listener/JobImpl.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/listener/JobImpl.java index 369eb2a4..220346c8 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/listener/JobImpl.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/listener/JobImpl.java @@ -1,16 +1,16 @@ /* - * Copyright 2022 Sonu Kumar + * Copyright (c) 2021-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -34,14 +34,14 @@ import com.github.sonus21.rqueue.utils.Constants; import com.github.sonus21.rqueue.utils.TimeoutUtils; import com.github.sonus21.rqueue.web.service.RqueueMessageMetadataService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.RedisSystemException; -import org.springframework.util.CollectionUtils; import java.io.Serializable; import java.time.Duration; import java.util.List; import java.util.UUID; import java.util.concurrent.Callable; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.RedisSystemException; +import org.springframework.util.CollectionUtils; @Slf4j @SuppressWarnings("java:S107") @@ -336,14 +336,14 @@ private void saveMessageMetadata(Callable callable) { void updateMessageStatus(MessageStatus messageStatus) { setMessageStatus(messageStatus); // We need to address these problems with message metadata - // 1. Message was deleted while executing, this means local copy is stale + // 1. The Message was deleted while executing; this means local copy is stale // 2. Parallel update is being made [dashboard operation, periodic job (two periodic jobs can // run in parallel due to failure)] if (!messageStatus.isTerminalState() || getRqueueMessage().isPeriodic()) { Duration duration = rqueueConfig.getMessageDurability(getRqueueMessage().getPeriod()); saveMessageMetadata( () -> { - messageMetadataService.save(getMessageMetadata(), duration); + messageMetadataService.save(getMessageMetadata(), duration, false); return null; }); } else { diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/utils/Constants.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/utils/Constants.java index 064d804c..05d42b51 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/utils/Constants.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/utils/Constants.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2020-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -54,10 +54,16 @@ public final class Constants { public static final int MIN_CONCURRENCY = 1; public static final long MINIMUM_JOB_PERIOD = 1000L; public static final String QUEUE_CRUD_LOCK_KEY_PREFIX = "q-crud::"; + public static final String MESSAGE_LOCK_KEY_PREFIX = "msg::"; - private Constants() {} + private Constants() { + } public static String getQueueCrudLockKey(RqueueConfig rqueueConfig, String queueName) { return rqueueConfig.getLockKey(QUEUE_CRUD_LOCK_KEY_PREFIX + queueName); } + + public static String getMessageLockName(RqueueConfig rqueueConfig, String messageId) { + return rqueueConfig.getLockKey(MESSAGE_LOCK_KEY_PREFIX + messageId); + } } diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/web/service/RqueueMessageMetadataService.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/web/service/RqueueMessageMetadataService.java index 67a75fd0..1dd29585 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/web/service/RqueueMessageMetadataService.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/web/service/RqueueMessageMetadataService.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2019-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -18,12 +18,11 @@ import com.github.sonus21.rqueue.core.RqueueMessage; import com.github.sonus21.rqueue.models.db.MessageMetadata; -import org.springframework.data.redis.core.ZSetOperations.TypedTuple; -import reactor.core.publisher.Mono; - import java.time.Duration; import java.util.Collection; import java.util.List; +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; +import reactor.core.publisher.Mono; public interface RqueueMessageMetadataService { @@ -35,7 +34,7 @@ public interface RqueueMessageMetadataService { List findAll(Collection ids); - void save(MessageMetadata messageMetadata, Duration ttl); + void save(MessageMetadata messageMetadata, Duration ttl, boolean checkUnique); MessageMetadata getByMessageId(String queueName, String messageId); @@ -43,7 +42,7 @@ public interface RqueueMessageMetadataService { MessageMetadata getOrCreateMessageMetadata(RqueueMessage rqueueMessage); - Mono saveReactive(MessageMetadata messageMetadata, Duration ttl); + Mono saveReactive(MessageMetadata messageMetadata, Duration ttl, boolean checkUnique); List> readMessageMetadataForQueue( String queueName, long start, long end); diff --git a/rqueue-core/src/main/java/com/github/sonus21/rqueue/web/service/impl/RqueueMessageMetadataServiceImpl.java b/rqueue-core/src/main/java/com/github/sonus21/rqueue/web/service/impl/RqueueMessageMetadataServiceImpl.java index 37bcc019..a84d437d 100644 --- a/rqueue-core/src/main/java/com/github/sonus21/rqueue/web/service/impl/RqueueMessageMetadataServiceImpl.java +++ b/rqueue-core/src/main/java/com/github/sonus21/rqueue/web/service/impl/RqueueMessageMetadataServiceImpl.java @@ -1,38 +1,45 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2020-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ package com.github.sonus21.rqueue.web.service.impl; import com.github.sonus21.rqueue.common.RqueueLockManager; +import com.github.sonus21.rqueue.config.RqueueConfig; import com.github.sonus21.rqueue.core.RqueueMessage; import com.github.sonus21.rqueue.core.support.RqueueMessageUtils; import com.github.sonus21.rqueue.dao.RqueueMessageMetadataDao; import com.github.sonus21.rqueue.dao.RqueueStringDao; import com.github.sonus21.rqueue.models.db.MessageMetadata; import com.github.sonus21.rqueue.models.enums.MessageStatus; +import com.github.sonus21.rqueue.utils.Constants; import com.github.sonus21.rqueue.web.service.RqueueMessageMetadataService; +import java.time.Duration; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.DefaultTypedTuple; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; -import java.time.Duration; -import java.util.*; -import java.util.stream.Collectors; @Service @Slf4j @@ -41,15 +48,18 @@ public class RqueueMessageMetadataServiceImpl implements RqueueMessageMetadataSe private final RqueueMessageMetadataDao rqueueMessageMetadataDao; private final RqueueStringDao rqueueStringDao; private final RqueueLockManager lockManager; + private final RqueueConfig rqueueConfig; @Autowired public RqueueMessageMetadataServiceImpl( RqueueMessageMetadataDao rqueueMessageMetadataDao, RqueueStringDao rqueueStringDao, - RqueueLockManager rqueueLockManager) { + RqueueLockManager rqueueLockManager, + RqueueConfig rqueueConfig) { this.rqueueMessageMetadataDao = rqueueMessageMetadataDao; this.rqueueStringDao = rqueueStringDao; this.lockManager = rqueueLockManager; + this.rqueueConfig = rqueueConfig; } @Override @@ -75,8 +85,8 @@ public List findAll(Collection ids) { } @Override - public void save(MessageMetadata messageMetadata, Duration duration) { - rqueueMessageMetadataDao.save(messageMetadata, duration); + public void save(MessageMetadata messageMetadata, Duration duration, boolean checkUnique) { + rqueueMessageMetadataDao.save(messageMetadata, duration, checkUnique); } @Override @@ -88,8 +98,9 @@ public MessageMetadata getByMessageId(String queueName, String messageId) { @Override public boolean deleteMessage(String queueName, String messageId, Duration duration) { String lockValue = UUID.randomUUID().toString(); + String lockKey = Constants.getMessageLockName(rqueueConfig, messageId); try { - if (lockManager.acquireLock(messageId, lockValue, Duration.ofSeconds(1))) { + if (lockManager.acquireLock(lockKey, lockValue, Duration.ofSeconds(1))) { String id = RqueueMessageUtils.getMessageMetaId(queueName, messageId); MessageMetadata messageMetadata = rqueueMessageMetadataDao.get(id); if (messageMetadata == null) { @@ -97,11 +108,14 @@ public boolean deleteMessage(String queueName, String messageId, Duration durati } messageMetadata.setDeleted(true); messageMetadata.setDeletedOn(System.currentTimeMillis()); - save(messageMetadata, duration); + save(messageMetadata, duration, false); + log.debug("message deleted, id: {}", id); return true; + } else { + log.error("Lock could not be acquired, Id: {}", messageId); } } finally { - lockManager.releaseLock(messageId, lockValue); + lockManager.releaseLock(lockKey, lockValue); } return false; } @@ -117,8 +131,9 @@ public MessageMetadata getOrCreateMessageMetadata(RqueueMessage rqueueMessage) { } @Override - public Mono saveReactive(MessageMetadata messageMetadata, Duration duration) { - return rqueueMessageMetadataDao.saveReactive(messageMetadata, duration); + public Mono saveReactive( + MessageMetadata messageMetadata, Duration duration, boolean isUnique) { + return rqueueMessageMetadataDao.saveReactive(messageMetadata, duration, isUnique); } @Override @@ -151,7 +166,7 @@ public List> readMessageMetadataForQueue( public void saveMessageMetadataForQueue( String queueName, MessageMetadata messageMetadata, Long ttlInMillisecond) { messageMetadata.setUpdatedOn(System.currentTimeMillis()); - save(messageMetadata, Duration.ofMillis(ttlInMillisecond)); + save(messageMetadata, Duration.ofMillis(ttlInMillisecond), false); rqueueStringDao.addToOrderedSetWithScore( queueName, messageMetadata.getId(), -(System.currentTimeMillis() + ttlInMillisecond)); } diff --git a/rqueue-core/src/test/java/com/github/sonus21/rqueue/core/impl/RqueueMessageManagerImplTest.java b/rqueue-core/src/test/java/com/github/sonus21/rqueue/core/impl/RqueueMessageManagerImplTest.java index 364f3fab..ceca3a83 100644 --- a/rqueue-core/src/test/java/com/github/sonus21/rqueue/core/impl/RqueueMessageManagerImplTest.java +++ b/rqueue-core/src/test/java/com/github/sonus21/rqueue/core/impl/RqueueMessageManagerImplTest.java @@ -1,28 +1,32 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2021-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ package com.github.sonus21.rqueue.core.impl; +import static org.apache.commons.lang3.reflect.FieldUtils.writeField; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; 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; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -30,82 +34,313 @@ import com.github.sonus21.TestBase; import com.github.sonus21.rqueue.CoreUnitTest; import com.github.sonus21.rqueue.common.RqueueLockManager; +import com.github.sonus21.rqueue.config.RqueueConfig; import com.github.sonus21.rqueue.core.DefaultRqueueMessageConverter; +import com.github.sonus21.rqueue.core.EndpointRegistry; +import com.github.sonus21.rqueue.core.RqueueMessage; import com.github.sonus21.rqueue.core.RqueueMessageManager; import com.github.sonus21.rqueue.core.RqueueMessageTemplate; import com.github.sonus21.rqueue.exception.LockCanNotBeAcquired; -import com.github.sonus21.rqueue.listener.RqueueMessageHeaders; +import com.github.sonus21.rqueue.listener.QueueDetail; +import com.github.sonus21.rqueue.models.MessageMoveResult; +import com.github.sonus21.rqueue.models.db.MessageMetadata; +import com.github.sonus21.rqueue.utils.MessageMetadataTestUtils; +import com.github.sonus21.rqueue.utils.PriorityUtils; import com.github.sonus21.rqueue.utils.TestUtils; import com.github.sonus21.rqueue.web.service.RqueueMessageMetadataService; +import java.time.Duration; +import java.util.Collections; +import java.util.List; import java.util.UUID; -import org.apache.commons.lang3.reflect.FieldUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.MessageConverter; @CoreUnitTest class RqueueMessageManagerImplTest extends TestBase { + private final MessageConverter messageConverter = new DefaultRqueueMessageConverter(); private final String messageId = UUID.randomUUID().toString(); - private final String queue = "test-queue"; - MessageConverter messageConverter = new DefaultRqueueMessageConverter(); - MessageHeaders messageHeaders = RqueueMessageHeaders.emptyMessageHeaders(); - @Mock private RqueueMessageTemplate messageTemplate; - @Mock private RqueueLockManager rqueueLockManager; - @Mock private RqueueMessageMetadataService rqueueMessageMetadataService; + private final String message = "Test Message"; + + + private final String queueName = "test-queue"; + private final String deadLetterQueueName = "dead-test-queue"; + private final MessageMetadata messageMetadata = MessageMetadataTestUtils.createMessageMetadata( + messageConverter, + queueName, message); + private final RqueueMessage rqueueMessage = messageMetadata.getRqueueMessage(); + private final QueueDetail queueDetail = TestUtils.createQueueDetail(queueName); + + + private final String queueName2 = "test-queue2"; + private final String priority = "high"; + private final String queueNameWithPriority = PriorityUtils.getQueueNameForPriority(queueName2, + priority); + private final QueueDetail queueDetail2 = TestUtils.createQueueDetail( + queueNameWithPriority, + Collections.singletonMap(priority, 5), 3, 15_000, ""); + + + private final MessageMetadata messageMetadata2 = MessageMetadataTestUtils.createMessageMetadata( + messageConverter, + queueNameWithPriority, message); + private final RqueueMessage rqueueMessage2 = messageMetadata2.getRqueueMessage(); + @Mock + private RqueueLockManager rqueueLockManager; + @Mock + private RqueueMessageMetadataService rqueueMessageMetadataService; + @Mock + private RqueueMessageTemplate rqueueMessageTemplate; + @Mock + private RqueueConfig rqueueConfig; + @Mock + private MessageSweeper messageSweeper; private RqueueMessageManager rqueueMessageManager; + @BeforeEach public void init() throws IllegalAccessException { MockitoAnnotations.openMocks(this); rqueueMessageManager = - new RqueueMessageManagerImpl(messageTemplate, messageConverter, messageHeaders); - FieldUtils.writeField( + new RqueueMessageManagerImpl( + rqueueMessageTemplate, messageConverter, null); + EndpointRegistry.delete(); + EndpointRegistry.register(queueDetail); + EndpointRegistry.register(queueDetail2); + writeField(rqueueMessageManager, "rqueueConfig", rqueueConfig, true); + writeField(rqueueMessageManager, "rqueueLockManager", rqueueLockManager, true); + writeField( rqueueMessageManager, "rqueueMessageMetadataService", rqueueMessageMetadataService, true); - FieldUtils.writeField(rqueueMessageManager, "rqueueLockManager", rqueueLockManager, true); + } + + + @Test + void deleteAllMessages() { + try (MockedStatic messageSweeperMockedStatic = Mockito.mockStatic( + MessageSweeper.class)) { + messageSweeperMockedStatic.when(() -> MessageSweeper.getInstance(any(), any(), any())) + .thenReturn(messageSweeper); + rqueueMessageManager.deleteAllMessages(queueName); + verify(messageSweeper, times(1)).deleteAllMessages(any()); + } + } + + @Test + void deleteAllMessagesWithPriority() { + try (MockedStatic messageSweeperMockedStatic = Mockito.mockStatic( + MessageSweeper.class)) { + messageSweeperMockedStatic.when(() -> MessageSweeper.getInstance(any(), any(), any())) + .thenReturn(messageSweeper); + rqueueMessageManager.deleteAllMessages(queueName2, priority); + verify(messageSweeper, times(1)).deleteAllMessages(any()); + } + } + + + @Test + void getAllMessages() { + doReturn(Collections.emptyList()).when(rqueueMessageTemplate) + .getAllMessages(queueDetail.getQueueName(), queueDetail.getProcessingQueueName(), + queueDetail.getScheduledQueueName()); + assertEquals(0, rqueueMessageManager.getAllMessages(queueName).size()); + doReturn(Collections.singletonList(rqueueMessage)).when(rqueueMessageTemplate) + .getAllMessages(queueDetail.getQueueName(), queueDetail.getProcessingQueueName(), + queueDetail.getScheduledQueueName()); + List messages = rqueueMessageManager.getAllMessages(queueName); + assertEquals(1, messages.size()); + assertEquals(message, messages.get(0)); + } + + @Test + void getAllMessagesWithPriority() { + doReturn(Collections.emptyList()).when(rqueueMessageTemplate) + .getAllMessages(queueDetail2.getQueueName(), queueDetail2.getProcessingQueueName(), + queueDetail2.getScheduledQueueName()); + assertEquals(0, rqueueMessageManager.getAllMessages(queueName2, priority).size()); + + doReturn(Collections.singletonList(rqueueMessage2)).when(rqueueMessageTemplate) + .getAllMessages(queueDetail2.getQueueName(), queueDetail2.getProcessingQueueName(), + queueDetail2.getScheduledQueueName()); + List messages = rqueueMessageManager.getAllMessages(queueName2, priority); + assertEquals(1, messages.size()); + assertEquals(message, messages.get(0)); } @Test - void getMessageDoesNotExist() { - assertNull(rqueueMessageManager.getMessage(queue, messageId)); + void getRqueueMessage() { + assertNull(rqueueMessageManager.getRqueueMessage(queueName, messageId)); verify(rqueueMessageMetadataService, times(1)).getByMessageId(anyString(), anyString()); + + doReturn(messageMetadata) + .when(rqueueMessageMetadataService) + .getByMessageId(queueName, messageId); + assertEquals(rqueueMessage, rqueueMessageManager.getRqueueMessage(queueName, messageId)); + } + + @Test + void getRqueueMessageWithPriority() { + assertNull(rqueueMessageManager.getRqueueMessage(queueName2, priority, messageId)); + verify(rqueueMessageMetadataService, times(1)).getByMessageId(anyString(), anyString()); + + doReturn(messageMetadata2) + .when(rqueueMessageMetadataService) + .getByMessageId(queueNameWithPriority, messageId); + assertEquals(rqueueMessage2, + rqueueMessageManager.getRqueueMessage(queueName2, priority, messageId)); + } + + + @Test + void getAllRqueueMessage() { + assertEquals(0, rqueueMessageManager.getAllRqueueMessage(queueName).size()); + verify(rqueueMessageTemplate, times(1)).getAllMessages(queueDetail.getQueueName(), + queueDetail.getProcessingQueueName(), queueDetail.getScheduledQueueName()); + + doReturn(Collections.singletonList(rqueueMessage)) + .when(rqueueMessageTemplate).getAllMessages(queueDetail.getQueueName(), + queueDetail.getProcessingQueueName(), queueDetail.getScheduledQueueName()); + List messages = rqueueMessageManager.getAllRqueueMessage(queueName); + assertEquals(1, messages.size()); + assertEquals(rqueueMessage, messages.get(0)); } @Test - void getRqueueMessageDoesNotExist() { - assertNull(rqueueMessageManager.getRqueueMessage(queue, messageId)); + void getAllRqueueMessageWithPriority() { + assertEquals(0, rqueueMessageManager.getAllRqueueMessage(queueName2, priority).size()); + verify(rqueueMessageTemplate, times(1)).getAllMessages(queueDetail2.getQueueName(), + queueDetail2.getProcessingQueueName(), queueDetail2.getScheduledQueueName()); + + doReturn(Collections.singletonList(rqueueMessage)) + .when(rqueueMessageTemplate).getAllMessages(queueDetail2.getQueueName(), + queueDetail2.getProcessingQueueName(), queueDetail2.getScheduledQueueName()); + List messages = rqueueMessageManager.getAllRqueueMessage(queueName2, priority); + assertEquals(1, messages.size()); + assertEquals(rqueueMessage, messages.get(0)); + } + + + @Test + void getMessage() { + assertNull(rqueueMessageManager.getMessage(queueName, messageId)); verify(rqueueMessageMetadataService, times(1)).getByMessageId(anyString(), anyString()); + + doReturn(messageMetadata) + .when(rqueueMessageMetadataService) + .getByMessageId(queueName, messageId); + assertNotNull(rqueueMessageManager.getMessage(queueName, messageId)); } @Test - void getMessageExist() { - doReturn(TestUtils.createMessageMetadata(messageConverter, queue)) + void getMessageWithPriority() { + assertNull(rqueueMessageManager.getMessage(queueName2, priority, messageId)); + verify(rqueueMessageMetadataService, times(1)).getByMessageId(anyString(), anyString()); + assertNull(rqueueMessageManager.getRqueueMessage(queueName2, priority, messageId)); + verify(rqueueMessageMetadataService, times(2)).getByMessageId(anyString(), anyString()); + doReturn(messageMetadata2) .when(rqueueMessageMetadataService) - .getByMessageId(queue, messageId); - assertNotNull(rqueueMessageManager.getMessage(queue, messageId)); + .getByMessageId(queueNameWithPriority, messageId); + assertNotNull(rqueueMessageManager.getMessage(queueName2, priority, messageId)); } @Test - void existLockCanNotBeAcquired() { - doReturn(false).when(rqueueLockManager).acquireLock(anyString(), anyString(), any()); - assertThrows(LockCanNotBeAcquired.class, () -> rqueueMessageManager.exist(queue, messageId)); + void exist() { + doReturn(messageMetadata) + .when(rqueueMessageMetadataService) + .getByMessageId(queueName, messageId); + assertTrue(rqueueMessageManager.exist(queueName, messageId)); + + doReturn(null) + .when(rqueueMessageMetadataService) + .getByMessageId(queueName, messageId); + assertFalse(rqueueMessageManager.exist(queueName, messageId)); } @Test - void existLockAcquiredAndMessageExist() { - doReturn(true).when(rqueueLockManager).acquireLock(anyString(), anyString(), any()); - doReturn(TestUtils.createMessageMetadata(messageConverter, queue)) + void existWithPriority() { + // entry wont exist + assertFalse(rqueueMessageManager.exist(queueName2, priority, messageId)); + doReturn(messageMetadata2) .when(rqueueMessageMetadataService) - .getByMessageId(queue, messageId); - assertTrue(rqueueMessageManager.exist(queue, messageId)); + .getByMessageId(queueNameWithPriority, messageId); + assertTrue(rqueueMessageManager.exist(queueName2, priority, messageId)); + } + + + @Test + void deleteMessage() { + assertFalse(rqueueMessageManager.deleteMessage(queueName, messageId)); + + doReturn(Duration.ofSeconds(500)).when(rqueueConfig).getMessageDurability(0L); + doReturn(messageMetadata).when(rqueueMessageMetadataService) + .getByMessageId(queueName, messageId); + assertFalse(rqueueMessageManager.deleteMessage(queueName, messageId)); + + doReturn(true).when(rqueueMessageMetadataService) + .deleteMessage(queueName, messageId, Duration.ofSeconds(500)); + assertTrue(rqueueMessageManager.deleteMessage(queueName, messageId)); + } + + @Test + void deleteMessageWithPriority() { + + assertFalse(rqueueMessageManager.deleteMessage(queueName2, messageId)); + + doReturn(Duration.ofSeconds(500)).when(rqueueConfig).getMessageDurability(0L); + doReturn(messageMetadata2).when(rqueueMessageMetadataService) + .getByMessageId(queueNameWithPriority, messageId); + assertFalse(rqueueMessageManager.deleteMessage(queueName2, priority, messageId)); + + doReturn(true).when(rqueueMessageMetadataService) + .deleteMessage(queueNameWithPriority, messageId, Duration.ofSeconds(500)); + assertTrue(rqueueMessageManager.deleteMessage(queueName2, priority, messageId)); + } + + @Test + void getMessageConverter() { + assertEquals(messageConverter.hashCode(), + rqueueMessageManager.getMessageConverter().hashCode()); + } + + + @Test + void moveMessageFromQueueExceptions() { + assertThrows(IllegalArgumentException.class, + () -> rqueueMessageManager.moveMessageFromDeadLetterToQueue(null, queueName, null)); + assertThrows(IllegalArgumentException.class, + () -> rqueueMessageManager.moveMessageFromDeadLetterToQueue(deadLetterQueueName, null, + null)); + doReturn(new MessageMoveResult(10, true)) + .when(rqueueMessageTemplate) + .moveMessageListToList(anyString(), anyString(), anyInt()); + rqueueMessageManager.moveMessageFromDeadLetterToQueue(deadLetterQueueName, queueName, null); + } + + @Test + void moveMessageFromDeadLetterToQueueDefaultSize() { + doReturn(new MessageMoveResult(100, true)) + .when(rqueueMessageTemplate) + .moveMessageListToList(anyString(), anyString(), anyInt()); + rqueueMessageManager.moveMessageFromDeadLetterToQueue(deadLetterQueueName, queueName, null); + verify(rqueueMessageTemplate, times(1)) + .moveMessageListToList(eq(deadLetterQueueName), eq(queueName), anyInt()); + verify(rqueueMessageTemplate, times(1)) + .moveMessageListToList(deadLetterQueueName, queueName, 100); } @Test - void existLockAcquiredAndMessageDoesNotExist() { - doReturn(true).when(rqueueLockManager).acquireLock(anyString(), anyString(), any()); - assertFalse(rqueueMessageManager.exist(queue, messageId)); + void moveMessageFromDeadLetterToQueueFixedSize() { + doReturn(new MessageMoveResult(10, true)) + .when(rqueueMessageTemplate) + .moveMessageListToList(anyString(), anyString(), anyInt()); + rqueueMessageManager.moveMessageFromDeadLetterToQueue(deadLetterQueueName, queueName, 10); + verify(rqueueMessageTemplate, times(1)) + .moveMessageListToList(eq(deadLetterQueueName), eq(queueName), anyInt()); + verify(rqueueMessageTemplate, times(1)) + .moveMessageListToList(eq(deadLetterQueueName), eq(queueName), eq(10)); } } diff --git a/rqueue-core/src/test/java/com/github/sonus21/rqueue/listener/JobImplTest.java b/rqueue-core/src/test/java/com/github/sonus21/rqueue/listener/JobImplTest.java index ab1c58ed..1e8add28 100644 --- a/rqueue-core/src/test/java/com/github/sonus21/rqueue/listener/JobImplTest.java +++ b/rqueue-core/src/test/java/com/github/sonus21/rqueue/listener/JobImplTest.java @@ -1,21 +1,38 @@ /* - * Copyright 2022 Sonu Kumar + * Copyright (c) 2021-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ package com.github.sonus21.rqueue.listener; +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.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import com.github.sonus21.TestBase; import com.github.sonus21.rqueue.CoreUnitTest; import com.github.sonus21.rqueue.common.RqueueLockManager; @@ -27,27 +44,22 @@ import com.github.sonus21.rqueue.dao.RqueueJobDao; import com.github.sonus21.rqueue.models.db.Execution; import com.github.sonus21.rqueue.models.db.MessageMetadata; +import com.github.sonus21.rqueue.models.db.RqueueJob; import com.github.sonus21.rqueue.models.enums.ExecutionStatus; import com.github.sonus21.rqueue.models.enums.JobStatus; import com.github.sonus21.rqueue.models.enums.MessageStatus; import com.github.sonus21.rqueue.utils.TestUtils; import com.github.sonus21.rqueue.web.service.RqueueMessageMetadataService; +import java.time.Duration; import org.apache.commons.lang3.reflect.FieldUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; -import org.mockito.stubbing.Answer; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.messaging.converter.MessageConverter; -import java.time.Duration; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; @CoreUnitTest @MockitoSettings(strictness = Strictness.LENIENT) @@ -60,16 +72,11 @@ class JobImplTest extends TestBase { private final MessageMetadata messageMetadata = new MessageMetadata(rqueueMessage, MessageStatus.PROCESSING); private final Object userMessage = "Test Object"; - @Mock - private RedisConnectionFactory redisConnectionFactory; - @Mock - private RqueueMessageMetadataService messageMetadataService; - @Mock - private RqueueJobDao rqueueJobDao; - @Mock - private RqueueMessageTemplate rqueueMessageTemplate; - @Mock - private RqueueLockManager rqueueLockManager; + @Mock private RedisConnectionFactory redisConnectionFactory; + @Mock private RqueueMessageMetadataService messageMetadataService; + @Mock private RqueueJobDao rqueueJobDao; + @Mock private RqueueMessageTemplate rqueueMessageTemplate; + @Mock private RqueueLockManager rqueueLockManager; private RqueueConfig rqueueConfig; @BeforeEach @@ -188,8 +195,8 @@ void updateMessageStatus() { job.updateMessageStatus(MessageStatus.PROCESSING); assertEquals(MessageStatus.PROCESSING, job.getMessageMetadata().getStatus()); assertEquals(JobStatus.PROCESSING, job.getStatus()); - verify(rqueueJobDao, times(1)).createJob(any(), any()); - verify(messageMetadataService, times(1)).save(any(), any()); + verify(rqueueJobDao, times(1)).createJob(any(RqueueJob.class), any(Duration.class)); + verify(messageMetadataService, times(1)).save(any(MessageMetadata.class), any(Duration.class), anyBoolean()); verify(rqueueJobDao, times(1)).save(any(), any()); } @@ -303,22 +310,25 @@ void testMessageMetadataIsDeleted() throws IllegalAccessException { @Test void testMessageWasDeletedWhileRunning() throws IllegalAccessException { doReturn(true).when(rqueueLockManager).acquireLock(anyString(), any(), any()); - MessageMetadata metadata = messageMetadata.toBuilder().deleted(true) - .status(MessageStatus.DELETED).build(); + MessageMetadata metadata = + messageMetadata.toBuilder().deleted(true).status(MessageStatus.DELETED).build(); doReturn(metadata).when(messageMetadataService).get(messageMetadata.getId()); JobImpl job = instance(); job.execute(); job.updateMessageStatus(MessageStatus.FAILED); verify(rqueueJobDao, times(1)).createJob(any(), any()); verify(rqueueJobDao, times(2)).save(any(), any()); - doAnswer(invocation -> { - MessageMetadata messageMetadata = invocation.getArgument(0); - assertTrue(messageMetadata.isDeleted()); - assertEquals(MessageStatus.DELETED, messageMetadata.getStatus()); - return null; - }).when(messageMetadataService).save( - any(MessageMetadata.class), - eq(Duration.ofMinutes(rqueueConfig.getMessageDurabilityInMinute()))); - + doAnswer( + invocation -> { + MessageMetadata messageMetadata = invocation.getArgument(0); + assertTrue(messageMetadata.isDeleted()); + assertEquals(MessageStatus.DELETED, messageMetadata.getStatus()); + return null; + }) + .when(messageMetadataService) + .save( + any(MessageMetadata.class), + eq(Duration.ofMinutes(rqueueConfig.getMessageDurabilityInMinute())), + eq(false)); } } diff --git a/rqueue-core/src/test/java/com/github/sonus21/rqueue/utils/MessageMetadataTestUtils.java b/rqueue-core/src/test/java/com/github/sonus21/rqueue/utils/MessageMetadataTestUtils.java new file mode 100644 index 00000000..f77ae607 --- /dev/null +++ b/rqueue-core/src/test/java/com/github/sonus21/rqueue/utils/MessageMetadataTestUtils.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 Sonu Kumar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +package com.github.sonus21.rqueue.utils; + + +import com.github.sonus21.rqueue.core.RqueueMessage; +import com.github.sonus21.rqueue.core.support.RqueueMessageUtils; +import com.github.sonus21.rqueue.models.db.MessageMetadata; +import com.github.sonus21.rqueue.models.enums.MessageStatus; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.messaging.converter.MessageConverter; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MessageMetadataTestUtils { + + public static MessageMetadata createMessageMetadata( + MessageConverter messageConverter, String queue) { + return new MessageMetadata(RqueueMessageTestUtils.createMessage(messageConverter, queue), + MessageStatus.ENQUEUED); + } + + public static MessageMetadata createMessageMetadata( + MessageConverter messageConverter, String queue, Object message) { + RqueueMessage rqueueMessage = RqueueMessageUtils.generateMessages( + messageConverter, message, queue, null, null, 1) + .get(0); + return new MessageMetadata(rqueueMessage, MessageStatus.ENQUEUED); + } + +} diff --git a/rqueue-core/src/test/java/com/github/sonus21/rqueue/utils/RqueueMessageTestUtils.java b/rqueue-core/src/test/java/com/github/sonus21/rqueue/utils/RqueueMessageTestUtils.java new file mode 100644 index 00000000..d4256677 --- /dev/null +++ b/rqueue-core/src/test/java/com/github/sonus21/rqueue/utils/RqueueMessageTestUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Sonu Kumar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ + +package com.github.sonus21.rqueue.utils; + +import com.github.sonus21.rqueue.core.DefaultRqueueMessageConverter; +import com.github.sonus21.rqueue.core.RqueueMessage; +import com.github.sonus21.rqueue.core.support.RqueueMessageUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.messaging.converter.MessageConverter; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class RqueueMessageTestUtils { + + public static RqueueMessage createMessage(String queueName) { + return createMessage(new DefaultRqueueMessageConverter(), queueName); + } + + + public static RqueueMessage createMessage(MessageConverter messageConverter, String queue) { + return RqueueMessageUtils.generateMessage(messageConverter, queue); + } +} diff --git a/rqueue-core/src/test/java/com/github/sonus21/rqueue/web/service/RqueueMessageMetadataServiceTest.java b/rqueue-core/src/test/java/com/github/sonus21/rqueue/web/service/RqueueMessageMetadataServiceTest.java index d8ba905e..0384a596 100644 --- a/rqueue-core/src/test/java/com/github/sonus21/rqueue/web/service/RqueueMessageMetadataServiceTest.java +++ b/rqueue-core/src/test/java/com/github/sonus21/rqueue/web/service/RqueueMessageMetadataServiceTest.java @@ -1,44 +1,55 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2020-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ package com.github.sonus21.rqueue.web.service; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verifyNoInteractions; + import com.github.sonus21.TestBase; import com.github.sonus21.rqueue.CoreUnitTest; import com.github.sonus21.rqueue.common.RqueueLockManager; +import com.github.sonus21.rqueue.config.RqueueConfig; import com.github.sonus21.rqueue.core.support.RqueueMessageUtils; import com.github.sonus21.rqueue.dao.RqueueMessageMetadataDao; import com.github.sonus21.rqueue.dao.RqueueStringDao; import com.github.sonus21.rqueue.models.db.MessageMetadata; import com.github.sonus21.rqueue.models.enums.MessageStatus; +import com.github.sonus21.rqueue.utils.Constants; import com.github.sonus21.rqueue.web.service.impl.RqueueMessageMetadataServiceImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; @CoreUnitTest class RqueueMessageMetadataServiceTest extends TestBase { @@ -47,6 +58,7 @@ class RqueueMessageMetadataServiceTest extends TestBase { @Mock private RqueueMessageMetadataDao rqueueMessageMetadataDao; @Mock private RqueueStringDao rqueueStringDao; @Mock private RqueueLockManager lockManager; + @Mock private RqueueConfig rqueueConfig; private RqueueMessageMetadataService rqueueMessageMetadataService; @BeforeEach @@ -54,7 +66,7 @@ public void init() { MockitoAnnotations.openMocks(this); rqueueMessageMetadataService = new RqueueMessageMetadataServiceImpl( - rqueueMessageMetadataDao, rqueueStringDao, lockManager); + rqueueMessageMetadataDao, rqueueStringDao, lockManager, rqueueConfig); } @Test @@ -82,8 +94,14 @@ void findAll() { @Test void deleteMessageShouldCreateMessageMetadata() { + doAnswer((Answer) invocationOnMock -> invocationOnMock.getArgument(0)) + .when(rqueueConfig) + .getLockKey(anyString()); String id = UUID.randomUUID().toString(); - doReturn(true).when(lockManager).acquireLock(eq(id), anyString(), eq(Duration.ofSeconds(1))); + doReturn(true) + .when(lockManager) + .acquireLock( + eq(Constants.MESSAGE_LOCK_KEY_PREFIX + id), anyString(), eq(Duration.ofSeconds(1))); doAnswer( invocation -> { MessageMetadata metadata = invocation.getArgument(0); @@ -92,14 +110,20 @@ void deleteMessageShouldCreateMessageMetadata() { return null; }) .when(rqueueMessageMetadataDao) - .save(any(), eq(Duration.ofDays(7))); + .save(any(), eq(Duration.ofDays(7)), eq(false)); assertTrue(rqueueMessageMetadataService.deleteMessage(queueName, id, Duration.ofDays(7))); } @Test void deleteMessage() { String id = UUID.randomUUID().toString(); - doReturn(true).when(lockManager).acquireLock(eq(id), anyString(), eq(Duration.ofSeconds(1))); + doAnswer((Answer) invocationOnMock -> invocationOnMock.getArgument(0)) + .when(rqueueConfig) + .getLockKey(anyString()); + doReturn(true) + .when(lockManager) + .acquireLock( + eq(Constants.MESSAGE_LOCK_KEY_PREFIX + id), anyString(), eq(Duration.ofSeconds(1))); MessageMetadata metadata = new MessageMetadata( RqueueMessageUtils.getMessageMetaId(queueName, id), MessageStatus.ENQUEUED); @@ -115,14 +139,20 @@ void deleteMessage() { return null; }) .when(rqueueMessageMetadataDao) - .save(any(), eq(Duration.ofDays(7))); + .save(any(), eq(Duration.ofDays(7)), eq(false)); assertTrue(rqueueMessageMetadataService.deleteMessage(queueName, id, Duration.ofDays(7))); } @Test void deleteMessageShouldFailDueToLock() { String id = UUID.randomUUID().toString(); - doReturn(false).when(lockManager).acquireLock(eq(id), anyString(), eq(Duration.ofSeconds(1))); + doAnswer((Answer) invocationOnMock -> invocationOnMock.getArgument(0)) + .when(rqueueConfig) + .getLockKey(anyString()); + doReturn(false) + .when(lockManager) + .acquireLock( + eq(Constants.MESSAGE_LOCK_KEY_PREFIX + id), anyString(), eq(Duration.ofSeconds(1))); assertFalse(rqueueMessageMetadataService.deleteMessage(queueName, id, Duration.ofDays(7))); verifyNoInteractions(rqueueMessageMetadataDao); } diff --git a/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/MessageDeduplicationTest.java b/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/MessageDeduplicationTest.java index 0aae722d..58589dbe 100644 --- a/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/MessageDeduplicationTest.java +++ b/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/MessageDeduplicationTest.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2020-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -27,10 +27,12 @@ import com.github.sonus21.rqueue.test.dto.Email; import com.github.sonus21.rqueue.test.dto.Notification; import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; +import org.springframework.util.Assert; @SpringBootTest @ContextConfiguration(classes = Application.class) @@ -40,6 +42,7 @@ "rqueue.retry.per.poll=20", "rqueue.scheduler.auto.start=true", "spring.redis.port=8009", + "use.system.redis=false", "mysql.db.name=MessageDeduplicationTest", "rqueue.metrics.count.failure=false", "rqueue.metrics.count.execution=false", @@ -57,16 +60,18 @@ void enqueueUnique() throws TimedOutException { @Test void enqueueUniqueIn() throws TimedOutException { Notification notification = Notification.newInstance(); - rqueueMessageEnqueuer.enqueueUniqueIn( - notificationQueue, notification.getId(), notification, 1000L); + Assertions.assertTrue( + rqueueMessageEnqueuer.enqueueUniqueIn( + notificationQueue, notification.getId(), notification, 1000L)); Notification newNotification = Notification.newInstance(); newNotification.setId(notification.getId()); sleep(100); - rqueueMessageEnqueuer.enqueueUniqueIn( - notificationQueue, newNotification.getId(), newNotification, 1000L); - waitFor(() -> getMessageCount(notificationQueue) == 0, 30_000, "notification to be sent"); + Assertions.assertFalse( + rqueueMessageEnqueuer.enqueueUniqueIn( + notificationQueue, newNotification.getId(), newNotification, 1000L)); + waitFor(() -> getMessageCount(notificationQueue) == 0, 60_000, "notification to be sent"); Notification notificationFromDb = - consumedMessageStore.getMessage(newNotification.getId(), Notification.class); - assertEquals(newNotification, notificationFromDb); + consumedMessageStore.getMessage(notification.getId(), Notification.class); + assertEquals(notification, notificationFromDb); } } diff --git a/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/MessageProcessorTest.java b/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/MessageProcessorTest.java index 48b97252..53ea1676 100644 --- a/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/MessageProcessorTest.java +++ b/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/MessageProcessorTest.java @@ -1,16 +1,16 @@ /* - * Copyright 2022 Sonu Kumar + * Copyright (c) 2021-2023 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -18,6 +18,7 @@ import static com.github.sonus21.rqueue.utils.TimeoutUtils.waitFor; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.github.sonus21.rqueue.exception.TimedOutException; import com.github.sonus21.rqueue.spring.boot.application.ApplicationWithMessageProcessor; @@ -29,12 +30,12 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.RetryingTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; +import org.springframework.util.Assert; @ContextConfiguration(classes = ApplicationWithMessageProcessor.class) @SpringBootTest @@ -113,19 +114,21 @@ void simpleMessageExecution() throws TimedOutException { assertEquals(0, discardMessageProcessor.count()); } - @RetryingTest(2) + @Test void manualDeletionMessageProcessorTest() throws TimedOutException { cleanQueue(notificationQueue); + deleteTestData(); Notification notification = Notification.newInstance(); failureManager.createFailureDetail(notification.getId(), -1, 3); String messageId = enqueueAtGetMessageId(notificationQueue, notification, System.currentTimeMillis() + 1000L); - rqueueMessageManager.deleteMessage(notificationQueue, messageId); + assertTrue(rqueueMessageManager.deleteMessage(notificationQueue, messageId)); waitFor( () -> { List messages = getAllMessages(notificationQueue); return !messages.contains(notification); }, + 30_000L, "message to be ignored"); assertEquals(1, preExecutionMessageProcessor.count()); assertEquals(1, manualDeletionMessageProcessor.count()); diff --git a/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/RqueueMessageTemplateTest.java b/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/RqueueMessageTemplateTest.java index 615c0f30..626d2325 100644 --- a/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/RqueueMessageTemplateTest.java +++ b/rqueue-spring-boot-starter/src/test/java/com/github/sonus21/rqueue/spring/boot/tests/integration/RqueueMessageTemplateTest.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2020-2023 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -47,7 +47,8 @@ class RqueueMessageTemplateTest extends SpringTestBase { @Test void moveMessageFromDeadLetterQueueToOriginalQueue() { enqueue(emailDeadLetterQueue, i -> Email.newInstance(), 10, true); - rqueueMessageSender.moveMessageFromDeadLetterToQueue(emailDeadLetterQueue, emailQueue); + assertTrue( + rqueueMessageManager.moveMessageFromDeadLetterToQueue(emailDeadLetterQueue, emailQueue)); assertEquals(10, stringRqueueRedisTemplate.getListSize(emailQueue).intValue()); assertEquals(0, stringRqueueRedisTemplate.getListSize(emailDeadLetterQueue).intValue()); } diff --git a/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/application/ApplicationWithMessageProcessor.java b/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/application/ApplicationWithMessageProcessor.java index 836ea7a0..9033b0a4 100644 --- a/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/application/ApplicationWithMessageProcessor.java +++ b/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/application/ApplicationWithMessageProcessor.java @@ -1,16 +1,16 @@ /* - * Copyright 2022 Sonu Kumar + * Copyright (c) 2021-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -30,12 +30,12 @@ public TestMessageProcessor deadLetterQueueMessageProcessor() { @Bean public TestMessageProcessor preExecutionMessageProcessor() { - return new TestMessageProcessor("PreEx"); + return new TestMessageProcessor("Pre"); } @Bean public TestMessageProcessor postExecutionMessageProcessor() { - return new TestMessageProcessor("PostEx"); + return new TestMessageProcessor("Post"); } @Bean @@ -51,7 +51,7 @@ public TestMessageProcessor discardMessageProcessor() { @Bean public SimpleRqueueListenerContainerFactory simpleRqueueListenerContainerFactory( @Qualifier("preExecutionMessageProcessor") TestMessageProcessor preExecutionMessageProcessor, - @Qualifier("postExecutionMessageProcessor") TestMessageProcessor postExecutionMessageProcessor, + @Qualifier("postExecutionMessageProcessor") TestMessageProcessor postExecutionMessageProcessor, @Qualifier("manualDeletionMessageProcessor") TestMessageProcessor manualDeletionMessageProcessor, @Qualifier("discardMessageProcessor") TestMessageProcessor discardMessageProcessor, @Qualifier("deadLetterQueueMessageProcessor") TestMessageProcessor deadLetterQueueMessageProcessor) { diff --git a/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/common/SpringTestBase.java b/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/common/SpringTestBase.java index ff46ad70..4c6ab889 100644 --- a/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/common/SpringTestBase.java +++ b/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/common/SpringTestBase.java @@ -1,16 +1,16 @@ /* - * Copyright 2022 Sonu Kumar + * Copyright (c) 2020-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -27,7 +27,6 @@ import com.github.sonus21.rqueue.core.RqueueMessage; import com.github.sonus21.rqueue.core.RqueueMessageEnqueuer; import com.github.sonus21.rqueue.core.RqueueMessageManager; -import com.github.sonus21.rqueue.core.RqueueMessageSender; import com.github.sonus21.rqueue.core.RqueueMessageTemplate; import com.github.sonus21.rqueue.core.support.RqueueMessageUtils; import com.github.sonus21.rqueue.dao.RqueueJobDao; @@ -57,11 +56,8 @@ @Slf4j public abstract class SpringTestBase extends TestBase { - - @Autowired protected RqueueMessageSender rqueueMessageSender; @Autowired protected RqueueMessageTemplate rqueueMessageTemplate; @Autowired protected RqueueConfig rqueueConfig; - @Autowired protected RqueueWebConfig rqueueWebConfig; @Autowired protected RqueueRedisTemplate stringRqueueRedisTemplate; @Autowired protected ConsumedMessageStore consumedMessageStore; @Autowired protected RqueueMessageListenerContainer rqueueMessageListenerContainer; @@ -258,13 +254,15 @@ protected void cleanQueue(String queue) { } } + protected void deleteTestData() { + consumedMessageStore.deleteAll(); + failureManager.deleteAll(); + } + protected boolean enqueue(String queueName, Object message) { if (reactiveEnabled) { return reactiveRqueueMessageEnqueuer.enqueue(queueName, message).block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueue(queueName, message); - } return rqueueMessageEnqueuer.enqueue(queueName, message) != null; } @@ -272,9 +270,6 @@ protected boolean enqueueAt(String queueName, Object message, Date instant) { if (reactiveEnabled) { return reactiveRqueueMessageEnqueuer.enqueueAt(queueName, message, instant).block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueAt(queueName, message, instant); - } return rqueueMessageEnqueuer.enqueueAt(queueName, message, instant) != null; } @@ -282,9 +277,6 @@ protected boolean enqueueAt(String queueName, Object message, Instant instant) { if (reactiveEnabled) { return reactiveRqueueMessageEnqueuer.enqueueAt(queueName, message, instant).block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueAt(queueName, message, instant); - } return rqueueMessageEnqueuer.enqueueAt(queueName, message, instant) != null; } @@ -292,9 +284,6 @@ protected boolean enqueueAt(String queueName, Object message, long delay) { if (reactiveEnabled) { return reactiveRqueueMessageEnqueuer.enqueueAt(queueName, message, delay).block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueAt(queueName, message, delay); - } return rqueueMessageEnqueuer.enqueueAt(queueName, message, delay) != null; } @@ -309,9 +298,6 @@ protected boolean enqueueIn(String queueName, Object message, long delay) { if (reactiveEnabled) { return reactiveRqueueMessageEnqueuer.enqueueIn(queueName, message, delay).block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueIn(queueName, message, delay); - } return rqueueMessageEnqueuer.enqueueIn(queueName, message, delay) != null; } @@ -320,9 +306,6 @@ protected boolean enqueueIn(String queueName, Object message, long delay, TimeUn return reactiveRqueueMessageEnqueuer.enqueueIn(queueName, message, delay, timeUnit).block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueIn(queueName, message, delay, timeUnit); - } return rqueueMessageEnqueuer.enqueueIn(queueName, message, delay, timeUnit) != null; } @@ -330,9 +313,6 @@ protected boolean enqueueIn(String queueName, Object message, Duration duration) if (reactiveEnabled) { return reactiveRqueueMessageEnqueuer.enqueueIn(queueName, message, duration).block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueIn(queueName, message, duration); - } return rqueueMessageEnqueuer.enqueueIn(queueName, message, duration) != null; } @@ -341,9 +321,6 @@ protected boolean enqueueWithPriority(String queueName, String priority, Object return reactiveRqueueMessageEnqueuer.enqueueWithPriority(queueName, priority, message).block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueWithPriority(queueName, priority, message); - } return rqueueMessageEnqueuer.enqueueWithPriority(queueName, priority, message) != null; } @@ -355,9 +332,6 @@ protected boolean enqueueInWithPriority( .block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueInWithPriority(queueName, priority, message, delay); - } return rqueueMessageEnqueuer.enqueueInWithPriority(queueName, priority, message, delay) != null; } @@ -369,9 +343,6 @@ protected boolean enqueueInWithPriority( .block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueInWithPriority(queueName, priority, message, delay, unit); - } return rqueueMessageEnqueuer.enqueueInWithPriority(queueName, priority, message, delay, unit) != null; } @@ -384,9 +355,6 @@ protected boolean enqueueInWithPriority( .block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueInWithPriority(queueName, priority, message, duration); - } return rqueueMessageEnqueuer.enqueueInWithPriority(queueName, priority, message, duration) != null; } @@ -399,9 +367,6 @@ protected boolean enqueueAtWithPriority( .block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueAtWithPriority(queueName, priority, message, date); - } return rqueueMessageEnqueuer.enqueueAtWithPriority(queueName, priority, message, date) != null; } @@ -413,9 +378,6 @@ protected boolean enqueueAtWithPriority( .block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueAtWithPriority(queueName, priority, message, date); - } return rqueueMessageEnqueuer.enqueueAtWithPriority(queueName, priority, message, date) != null; } @@ -427,9 +389,6 @@ protected boolean enqueueAtWithPriority( .block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueAtWithPriority(queueName, priority, message, instant); - } return rqueueMessageEnqueuer.enqueueAtWithPriority(queueName, priority, message, instant) != null; } @@ -439,18 +398,11 @@ protected boolean enqueueWithRetry(String queueName, Object message, int retry) return reactiveRqueueMessageEnqueuer.enqueueWithRetry(queueName, message, retry).block() != null; } - if (random.nextBoolean()) { - return rqueueMessageSender.enqueueWithRetry(queueName, message, retry); - } return rqueueMessageEnqueuer.enqueueWithRetry(queueName, message, retry) != null; } protected void registerQueue(String queue, String... priorities) { - if (random.nextBoolean()) { - rqueueMessageSender.registerQueue(queue, priorities); - } else { - rqueueEndpointManager.registerQueue(queue, priorities); - } + rqueueEndpointManager.registerQueue(queue, priorities); } protected List getProcessingMessages(String queueName) { @@ -459,16 +411,10 @@ protected List getProcessingMessages(String queueName) { } protected List getAllMessages(String queueName) { - if (random.nextBoolean()) { - return rqueueMessageSender.getAllMessages(queueName); - } return rqueueMessageManager.getAllMessages(queueName); } private void deleteAllMessageInternal(String queueName) throws TimedOutException { - if (random.nextBoolean()) { - rqueueMessageSender.deleteAllMessages(queueName); - } rqueueMessageManager.deleteAllMessages(queueName); TimeoutUtils.waitFor(() -> getMessageCount(queueName) == 0, "message deletion"); } diff --git a/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/service/ConsumedMessageStore.java b/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/service/ConsumedMessageStore.java index 7ac7ee49..135e2f86 100644 --- a/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/service/ConsumedMessageStore.java +++ b/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/service/ConsumedMessageStore.java @@ -1,16 +1,16 @@ /* - * Copyright 2022 Sonu Kumar + * Copyright (c) 2019-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -27,6 +27,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -38,10 +39,12 @@ @Slf4j public class ConsumedMessageStore { - @NonNull - private final ConsumedMessageRepository consumedMessageRepository; - @NonNull - private final ObjectMapper objectMapper; + @NonNull private final ConsumedMessageRepository consumedMessageRepository; + @NonNull private final ObjectMapper objectMapper; + + public void deleteAll() { + consumedMessageRepository.deleteAll(); + } public void save(BaseQueueMessage message, Object tag, String queueName) throws JsonProcessingException { @@ -95,6 +98,10 @@ public Map getMessages(Collection messageIds, Class tC return idToMessage; } + public List getAllMessages(String messageId) { + return consumedMessageRepository.findByMessageIdIn(Collections.singletonList(messageId)); + } + public Map getMessages(Collection messageIds) { Iterable consumedMessages = consumedMessageRepository.findByMessageIdIn(messageIds); @@ -104,22 +111,29 @@ public Map getMessages(Collection messageIds) { return idToMessage; } - public List getAllMessages() { - List consumedMessages = new ArrayList<>(); - for (ConsumedMessage consumedMessage : consumedMessageRepository.findAll()) { - consumedMessages.add(consumedMessage); + public ConsumedMessage getConsumedMessage(String messageId) { + List messages = getConsumedMessages(messageId); + if (messages.isEmpty()) { + return null; } - return consumedMessages; + if (messages.size() == 1) { + return messages.get(0); + } + throw new IllegalStateException("more than one record found"); } - public ConsumedMessage getConsumedMessage(String messageId) { + public ConsumedMessage getConsumedMessage(String messageId, String tag) { List messages = getConsumedMessages(messageId); if (messages.isEmpty()) { - return null; + throw new IllegalStateException("no message found"); } + messages = messages.stream().filter(e -> tag.equals(e.getTag())).collect(Collectors.toList()); if (messages.size() == 1) { return messages.get(0); } + if (messages.isEmpty()) { + throw new IllegalStateException("no message found"); + } throw new IllegalStateException("more than one record found"); } diff --git a/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/service/FailureManager.java b/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/service/FailureManager.java index 39751413..9ca489d0 100644 --- a/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/service/FailureManager.java +++ b/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/service/FailureManager.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2019-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -22,6 +22,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @Service @@ -30,6 +31,10 @@ public class FailureManager { @NonNull private final FailureDetailRepository failureDetailRepository; + public void deleteAll() { + failureDetailRepository.deleteAll(); + } + public boolean shouldFail(String id) { // no entry so no fail FailureDetail failureDetail = failureDetailRepository.findById(id).orElse(null); diff --git a/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/tests/RetryTests.java b/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/tests/RetryTests.java index f4a621ab..343ab283 100644 --- a/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/tests/RetryTests.java +++ b/rqueue-spring-common-test/src/main/java/com/github/sonus21/rqueue/test/tests/RetryTests.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2019-2023 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -41,7 +41,7 @@ protected void verifyAfterNRetryTaskIsDeletedFromProcessingQueue() throws TimedO cleanQueue(jobQueue); Job job = Job.newInstance(); failureManager.createFailureDetail(job.getId(), 3, 10); - rqueueMessageSender.put(jobQueue, job); + rqueueMessageEnqueuer.enqueue(jobQueue, job); waitFor( () -> { Job jobInDb = consumedMessageStore.getMessage(job.getId(), Job.class); @@ -61,7 +61,7 @@ protected void verifyMessageMovedToDeadLetterQueue() throws TimedOutException { Email email = Email.newInstance(); failureManager.createFailureDetail(email.getId(), -1, 0); log.debug("queue: {} msg: {}", emailQueue, email); - rqueueMessageSender.put(emailQueue, email, 1000L); + rqueueMessageEnqueuer.enqueueIn(emailQueue, email, 1000L); waitFor( () -> emailRetryCount == failureManager.getFailureCount(email.getId()), 30000000, diff --git a/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/services/FailureDetailRepositoryImpl.java b/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/services/FailureDetailRepositoryImpl.java index 79cd2051..ff71865d 100644 --- a/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/services/FailureDetailRepositoryImpl.java +++ b/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/services/FailureDetailRepositoryImpl.java @@ -1,33 +1,36 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2019-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ package com.github.sonus21.rqueue.spring.services; +import com.github.sonus21.rqueue.test.entity.ConsumedMessage; import com.github.sonus21.rqueue.test.entity.FailureDetail; import com.github.sonus21.rqueue.test.repository.FailureDetailRepository; -import java.util.List; -import java.util.Optional; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaDelete; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; +import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.query.Query; import org.springframework.stereotype.Repository; import org.springframework.util.CollectionUtils; @@ -92,14 +95,25 @@ public long count() { } @Override - public void deleteById(String s) {} + public void deleteById(String s) { + } @Override - public void delete(FailureDetail entity) {} + public void delete(FailureDetail entity) { + } @Override - public void deleteAll(Iterable entities) {} + public void deleteAll(Iterable entities) { + } @Override - public void deleteAll() {} + public void deleteAll() { + EntityManager entityManager = entityManagerFactory.createEntityManager(); + Session session = entityManager.unwrap(Session.class); + CriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaDelete cr = cb.createCriteriaDelete(FailureDetail.class); + Query query = session.createQuery(cr); + query.getSingleResult(); + entityManager.close(); + } } diff --git a/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/tests/integration/SpringAppTest.java b/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/tests/integration/SpringAppTest.java index 3bc831ad..76e73cd8 100644 --- a/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/tests/integration/SpringAppTest.java +++ b/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/tests/integration/SpringAppTest.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2020-2025 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -53,20 +53,20 @@ @WebAppConfiguration @TestPropertySource( properties = { - "spring.redis.port=7004", - "mysql.db.name=SpringAppTest", - "sms.queue.active=true", - "notification.queue.active=false", - "email.queue.active=true", - "job.queue.active=true", - "use.system.redis=false", - "priority.mode=STRICT", - "reservation.queue.active=true", - "feed.generation.queue.active=true", - "chat.indexing.queue.active=true", - "provide.executor=true", - "email.queue.retry.count=-1", - "rqueue.retry.per.poll=10" + "spring.redis.port=7004", + "mysql.db.name=SpringAppTest", + "sms.queue.active=true", + "notification.queue.active=false", + "email.queue.active=true", + "job.queue.active=true", + "use.system.redis=false", + "priority.mode=STRICT", + "reservation.queue.active=true", + "feed.generation.queue.active=true", + "chat.indexing.queue.active=true", + "provide.executor=true", + "email.queue.retry.count=-1", + "rqueue.retry.per.poll=10" }) @SpringIntegrationTest class SpringAppTest extends AllQueueMode { @@ -109,7 +109,7 @@ void verifyDefaultDeadLetterQueueRetry() throws TimedOutException { Email email1 = (Email) RqueueMessageUtils.convertMessageToObject( - messages.get(0), rqueueMessageSender.getMessageConverter()); + messages.get(0), rqueueMessageManager.getMessageConverter()); assertEquals(email.getId(), email1.getId()); } @@ -129,63 +129,64 @@ void onlyPushMode() { registerQueue(emailQueue); registerQueue(smsQueue, critical, high, low); Email[] emails = - new Email[] { - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), - Email.newInstance(), + new Email[]{ + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), + Email.newInstance(), }; Sms[] sms = - new Sms[] { - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), - Sms.newInstance(), + new Sms[]{ + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), + Sms.newInstance(), }; assertTrue(enqueue(emailQueue, emails[0])); assertTrue(rqueueMessageEnqueuer.enqueue(emailQueue, emails[1].getId(), emails[1])); assertTrue(rqueueMessageEnqueuer.enqueueUnique(emailQueue, emails[2].getId(), emails[2])); + assertFalse(rqueueMessageEnqueuer.enqueueUnique(emailQueue, emails[2].getId(), emails[2])); assertNotNull(rqueueMessageEnqueuer.enqueueWithRetry(emailQueue, emails[3], 3)); assertTrue(rqueueMessageEnqueuer.enqueueWithRetry(emailQueue, emails[4].getId(), emails[4], 3)); @@ -233,6 +234,9 @@ void onlyPushMode() { assertTrue( rqueueMessageEnqueuer.enqueueUniqueIn( emailQueue, emails[18].getId(), emails[18], Constants.ONE_MILLI)); + assertFalse( + rqueueMessageEnqueuer.enqueueUniqueIn( + emailQueue, emails[18].getId(), emails[18], Constants.ONE_MILLI)); assertNotNull( rqueueMessageEnqueuer.enqueueInWithRetry(emailQueue, emails[19], 3, Constants.ONE_MILLI)); diff --git a/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/tests/unit/RqueueMessageConfigTest.java b/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/tests/unit/RqueueMessageConfigTest.java index eca53086..01e5cfc9 100644 --- a/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/tests/unit/RqueueMessageConfigTest.java +++ b/rqueue-spring/src/test/java/com/github/sonus21/rqueue/spring/tests/unit/RqueueMessageConfigTest.java @@ -1,16 +1,16 @@ /* - * Copyright 2021 Sonu Kumar + * Copyright (c) 2019-2023 Sonu Kumar * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. * */ @@ -45,12 +45,18 @@ class RqueueMessageConfigTest extends TestBase { private final List messageConverterList = new ArrayList<>(); - @Mock RqueueMessageHandler rqueueMessageHandler; - @Mock private SimpleRqueueListenerContainerFactory simpleRqueueListenerContainerFactory; - @Mock private BeanFactory beanFactory; - @Mock private RqueueMessageTemplate rqueueMessageTemplate; - @Mock private RedisConnectionFactory redisConnectionFactory; - @InjectMocks private RqueueListenerConfig rqueueMessageConfig; + @Mock + RqueueMessageHandler rqueueMessageHandler; + @Mock + private SimpleRqueueListenerContainerFactory simpleRqueueListenerContainerFactory; + @Mock + private BeanFactory beanFactory; + @Mock + private RqueueMessageTemplate rqueueMessageTemplate; + @Mock + private RedisConnectionFactory redisConnectionFactory; + @InjectMocks + private RqueueListenerConfig rqueueMessageConfig; @BeforeEach public void init() { @@ -106,7 +112,7 @@ void rqueueMessageSenderWithMessageTemplate() throws IllegalAccessException { doReturn(new DefaultRqueueMessageConverter()).when(rqueueMessageHandler).getMessageConverter(); RqueueListenerConfig messageConfig = new RqueueListenerConfig(); FieldUtils.writeField(messageConfig, "simpleRqueueListenerContainerFactory", factory, true); - assertNotNull(messageConfig.rqueueMessageSender(rqueueMessageHandler, rqueueMessageTemplate)); + assertNotNull(messageConfig.rqueueMessageEnqueuer(rqueueMessageHandler, rqueueMessageTemplate)); assertEquals(factory.getRqueueMessageTemplate().hashCode(), rqueueMessageTemplate.hashCode()); } }