Skip to content

Commit ec0677b

Browse files
committed
'Version 1.0.4 of the Amazon SQS Java Messaging Library'
1 parent 9004d64 commit ec0677b

13 files changed

+335
-95
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ for communicating with Amazon Simple Queue Service. This project builds on top o
1515
<dependency>
1616
<groupId>com.amazonaws</groupId>
1717
<artifactId>amazon-sqs-java-messaging-lib</artifactId>
18-
<version>1.0.3</version>
18+
<version>1.0.4</version>
1919
<type>jar</type>
2020
</dependency>
2121
```

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.amazonaws</groupId>
88
<artifactId>amazon-sqs-java-messaging-lib</artifactId>
9-
<version>1.0.3</version>
9+
<version>1.0.4</version>
1010
<packaging>jar</packaging>
1111
<name>Amazon SQS Java Messaging Library</name>
1212
<description>The Amazon SQS Java Messaging Library holds the Java Message Service compatible classes, that are used

src/main/java/com/amazon/sqs/javamessaging/PrefetchManager.java

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ public interface PrefetchManager {
2727
*/
2828
public void messageDispatched();
2929

30+
/**
31+
* Notify the prefetchThread that the message listener has finished with any
32+
* previous message and is ready to accept another.
33+
*/
34+
public void messageListenerReady();
35+
3036
/**
3137
* This is used to determine the state of the consumer, when the message
3238
* listener scheduler is processing the messages.

src/main/java/com/amazon/sqs/javamessaging/ProviderConfiguration.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ public int getNumberOfMessagesToPrefetch() {
2727
}
2828

2929
public void setNumberOfMessagesToPrefetch(int numberOfMessagesToPrefetch) {
30-
if (numberOfMessagesToPrefetch < SQSMessagingClientConstants.MIN_BATCH) {
31-
throw new IllegalArgumentException(String.format("Invalid prefetch size. Provided value '%1$s' cannot be smaller than '%2$s'", numberOfMessagesToPrefetch, SQSMessagingClientConstants.MIN_BATCH));
30+
if (numberOfMessagesToPrefetch < SQSMessagingClientConstants.MIN_PREFETCH) {
31+
throw new IllegalArgumentException(String.format("Invalid prefetch size. Provided value '%1$s' cannot be smaller than '%2$s'", numberOfMessagesToPrefetch, SQSMessagingClientConstants.MIN_PREFETCH));
3232
}
3333
this.numberOfMessagesToPrefetch = numberOfMessagesToPrefetch;
3434
}

src/main/java/com/amazon/sqs/javamessaging/SQSConnection.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,8 @@
1818
import java.util.Set;
1919
import java.util.concurrent.ConcurrentHashMap;
2020

21-
2221
import javax.jms.IllegalStateException;
23-
2422
import javax.jms.Connection;
25-
2623
import javax.jms.ConnectionConsumer;
2724
import javax.jms.ConnectionMetaData;
2825
import javax.jms.Destination;
@@ -93,7 +90,8 @@ public class SQSConnection implements Connection, QueueConnection {
9390

9491
/**
9592
* Configures the amount of messages that can be prefetched by a consumer. A
96-
* single consumer cannot prefetch more than 10 messages.
93+
* single consumer cannot prefetch more than 10 messages in a single call to SQS,
94+
* but it will make multiple calls as necessary.
9795
*/
9896
private final int numberOfMessagesToPrefetch;
9997
private volatile boolean closed = false;

src/main/java/com/amazon/sqs/javamessaging/SQSMessageConsumerPrefetch.java

+89-20
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
*/
1515
package com.amazon.sqs.javamessaging;
1616

17+
import java.net.URI;
1718
import java.util.ArrayDeque;
1819
import java.util.ArrayList;
1920
import java.util.Iterator;
2021
import java.util.List;
2122
import java.util.Set;
2223
import java.util.UUID;
2324

25+
import javax.jms.Destination;
2426
import javax.jms.JMSException;
2527
import javax.jms.MessageListener;
2628

@@ -92,7 +94,14 @@ public class SQSMessageConsumerPrefetch implements Runnable, PrefetchManager {
9294
* Counter on how many messages are prefetched into internal messageQueue.
9395
*/
9496
protected int messagesPrefetched = 0;
95-
97+
98+
/**
99+
* Counter on how many messages have been explicitly requested.
100+
* TODO: Consider renaming this class and several other variables now that
101+
* this logic factors in message requests as well as prefetching.
102+
*/
103+
protected int messagesRequested = 0;
104+
96105
/**
97106
* States of the prefetch thread
98107
*/
@@ -163,9 +172,24 @@ protected void setMessageListener(MessageListener messageListener) {
163172
List<MessageManager> allPrefetchedMessages = new ArrayList<MessageManager>(messageQueue);
164173
sqsSessionRunnable.scheduleCallBacks(messageListener, allPrefetchedMessages);
165174
messageQueue.clear();
175+
176+
// This will request the first message if necessary.
177+
// TODO: This may overfetch if setMessageListener is being called multiple
178+
// times, as the session callback scheduler may already have entries for this consumer.
179+
messageListenerReady();
166180
}
167181
}
168182

183+
/**
184+
* Determine the number of messages we should attempt to fetch from SQS.
185+
* Returns the difference between the number of messages needed (either for
186+
* prefetching or by request) and the number currently fetched.
187+
*/
188+
private int numberOfMessagesToFetch() {
189+
int numberOfMessagesNeeded = Math.max(numberOfMessagesToPrefetch, messagesRequested);
190+
return Math.max(numberOfMessagesNeeded - messagesPrefetched, 0);
191+
}
192+
169193
/**
170194
* Runs until the message consumer is closed and in-progress SQS
171195
* <code>receiveMessage</code> call returns.
@@ -190,8 +214,7 @@ public void run() {
190214
synchronized (stateLock) {
191215
waitForStart();
192216
waitForPrefetch();
193-
prefetchBatchSize = Math.min(
194-
(numberOfMessagesToPrefetch - messagesPrefetched), SQSMessagingClientConstants.MAX_BATCH);
217+
prefetchBatchSize = Math.min(numberOfMessagesToFetch(), SQSMessagingClientConstants.MAX_BATCH);
195218
}
196219

197220
if (!isClosed()) {
@@ -290,7 +313,7 @@ protected void processReceivedMessages(List<Message> messages) {
290313

291314
protected void waitForPrefetch() throws InterruptedException {
292315
synchronized (stateLock) {
293-
while (messagesPrefetched >= numberOfMessagesToPrefetch && !isClosed()) {
316+
while (numberOfMessagesToFetch() <= 0 && !isClosed()) {
294317
try {
295318
stateLock.wait();
296319
} catch (InterruptedException e) {
@@ -332,7 +355,20 @@ protected javax.jms.Message convertToJMSMessage(Message message) throws JMSExcep
332355
throw new JMSException("Not a supported JMS message type");
333356
}
334357
}
358+
335359
jmsMessage.setJMSDestination(sqsDestination);
360+
361+
MessageAttributeValue replyToQueueNameAttribute = message.getMessageAttributes().get(
362+
SQSMessage.JMS_SQS_REPLY_TO_QUEUE_NAME);
363+
MessageAttributeValue replyToQueueUrlAttribute = message.getMessageAttributes().get(
364+
SQSMessage.JMS_SQS_REPLY_TO_QUEUE_URL);
365+
if (replyToQueueNameAttribute != null && replyToQueueUrlAttribute != null) {
366+
String replyToQueueUrl = replyToQueueUrlAttribute.getStringValue();
367+
String replyToQueueName = replyToQueueNameAttribute.getStringValue();
368+
Destination replyToQueue = new SQSQueueDestination(replyToQueueName, replyToQueueUrl);
369+
jmsMessage.setJMSReplyTo(replyToQueue);
370+
}
371+
336372
return jmsMessage;
337373
}
338374

@@ -366,12 +402,38 @@ protected void waitForStart() throws InterruptedException {
366402
public void messageDispatched() {
367403
synchronized (stateLock) {
368404
messagesPrefetched--;
369-
if (messagesPrefetched < numberOfMessagesToPrefetch) {
405+
messagesRequested--;
406+
if (numberOfMessagesToFetch() > 0) {
370407
notifyStateChange();
371408
}
372409
}
373410
}
374411

412+
@Override
413+
public void messageListenerReady() {
414+
synchronized (stateLock) {
415+
// messagesRequested may still be more than zero if there were pending receive()
416+
// calls when the message listener was set.
417+
if (messagesRequested <= 0 && !isClosed() && messageListener != null) {
418+
requestMessage();
419+
}
420+
}
421+
}
422+
423+
void requestMessage() {
424+
synchronized (stateLock) {
425+
messagesRequested++;
426+
notifyStateChange();
427+
}
428+
}
429+
430+
private void unrequestMessage() {
431+
synchronized (stateLock) {
432+
messagesRequested--;
433+
notifyStateChange();
434+
}
435+
}
436+
375437
public static class MessageManager {
376438

377439
private final PrefetchManager prefetchManager;
@@ -405,28 +467,35 @@ javax.jms.Message receive(long timeout) throws JMSException {
405467
timeout = 0;
406468
}
407469

408-
MessageManager messageManager;
470+
MessageManager messageManager = null;
409471
synchronized (stateLock) {
410472
// If message exists in queue poll.
411473
if (!messageQueue.isEmpty()) {
412474
messageManager = messageQueue.pollFirst();
413475
} else {
414-
long startTime = System.currentTimeMillis();
415-
416-
long waitTime = 0;
417-
while (messageQueue.isEmpty() && !isClosed() &&
418-
(timeout == 0 || (waitTime = getWaitTime(timeout, startTime)) > 0)) {
419-
try {
420-
stateLock.wait(waitTime);
421-
} catch (InterruptedException e) {
422-
Thread.currentThread().interrupt();
476+
requestMessage();
477+
try {
478+
long startTime = System.currentTimeMillis();
479+
480+
long waitTime = 0;
481+
while (messageQueue.isEmpty() && !isClosed() &&
482+
(timeout == 0 || (waitTime = getWaitTime(timeout, startTime)) > 0)) {
483+
try {
484+
stateLock.wait(waitTime);
485+
} catch (InterruptedException e) {
486+
Thread.currentThread().interrupt();
487+
return null;
488+
}
489+
}
490+
if (messageQueue.isEmpty() || isClosed()) {
423491
return null;
424492
}
425-
}
426-
if (messageQueue.isEmpty() || isClosed()) {
427-
return null;
428-
}
429-
messageManager = messageQueue.pollFirst();
493+
messageManager = messageQueue.pollFirst();
494+
} finally {
495+
if (messageManager == null) {
496+
unrequestMessage();
497+
}
498+
}
430499
}
431500
}
432501
return messageHandler(messageManager);

src/main/java/com/amazon/sqs/javamessaging/SQSMessageProducer.java

+43-12
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@
3434

3535
import com.amazon.sqs.javamessaging.message.SQSBytesMessage;
3636
import com.amazon.sqs.javamessaging.message.SQSMessage;
37+
import com.amazon.sqs.javamessaging.message.SQSMessage.JMSMessagePropertyValue;
3738
import com.amazon.sqs.javamessaging.message.SQSObjectMessage;
3839
import com.amazon.sqs.javamessaging.message.SQSTextMessage;
39-
import com.amazon.sqs.javamessaging.message.SQSMessage.JMSMessagePropertyValue;
40-
import com.amazonaws.util.Base64;
4140
import com.amazonaws.services.sqs.model.MessageAttributeValue;
4241
import com.amazonaws.services.sqs.model.SendMessageRequest;
4342
import com.amazonaws.services.sqs.model.SendMessageResult;
43+
import com.amazonaws.util.Base64;
4444

4545
/**
4646
* A client uses a MessageProducer object to send messages to a queue
@@ -109,7 +109,15 @@ void sendInternal(SQSQueueDestination queue, Message rawMessage) throws JMSExcep
109109
throw new JMSException("Message body cannot be null or empty");
110110
}
111111
Map<String, MessageAttributeValue> messageAttributes = propertyToMessageAttribute((SQSMessage) message);
112-
addMessageTypeReservedAttribute(messageAttributes, (SQSMessage) message, messageType);
112+
113+
/**
114+
* These will override existing attributes if they exist. Everything that
115+
* has prefix JMS_ is reserved for JMS Provider, but if the user sets that
116+
* attribute, it will be overwritten.
117+
*/
118+
addStringAttribute(messageAttributes, SQSMessage.JMS_SQS_MESSAGE_TYPE, messageType);
119+
addReplyToQueueReservedAttributes(messageAttributes, message);
120+
113121
SendMessageRequest sendMessageRequest = new SendMessageRequest(queue.getQueueUrl(), sqsMessageBody);
114122
sendMessageRequest.setMessageAttributes(messageAttributes);
115123

@@ -218,19 +226,42 @@ Map<String, MessageAttributeValue> propertyToMessageAttribute(SQSMessage message
218226
private void addMessageTypeReservedAttribute(Map<String, MessageAttributeValue> messageAttributes,
219227
SQSMessage message, String value) throws JMSException {
220228

221-
MessageAttributeValue messageAttributeValue = new MessageAttributeValue();
222229

223-
messageAttributeValue.setDataType(SQSMessagingClientConstants.STRING);
224-
messageAttributeValue.setStringValue(value);
230+
addStringAttribute(messageAttributes, SQSMessage.JMS_SQS_MESSAGE_TYPE, value);
231+
}
225232

226-
/**
227-
* This will override the existing attribute if exists. Everything that
228-
* has prefix JMS_ is reserved for JMS Provider, but if the user sets that
229-
* attribute, it will be overwritten.
230-
*/
231-
messageAttributes.put(SQSMessage.JMS_SQS_MESSAGE_TYPE, messageAttributeValue);
233+
/**
234+
* Adds the reply-to queue name and url attributes during send as part of the send message
235+
* request, if necessary
236+
*/
237+
private void addReplyToQueueReservedAttributes(Map<String, MessageAttributeValue> messageAttributes,
238+
SQSMessage message) throws JMSException {
239+
240+
Destination replyTo = message.getJMSReplyTo();
241+
if (replyTo instanceof SQSQueueDestination) {
242+
SQSQueueDestination replyToQueue = (SQSQueueDestination)replyTo;
243+
244+
/**
245+
* This will override the existing attributes if exists. Everything that
246+
* has prefix JMS_ is reserved for JMS Provider, but if the user sets that
247+
* attribute, it will be overwritten.
248+
*/
249+
addStringAttribute(messageAttributes, SQSMessage.JMS_SQS_REPLY_TO_QUEUE_NAME, replyToQueue.getQueueName());
250+
addStringAttribute(messageAttributes, SQSMessage.JMS_SQS_REPLY_TO_QUEUE_URL, replyToQueue.getQueueUrl());
251+
}
232252
}
233253

254+
/**
255+
* Convenience method for adding a single string attribute.
256+
*/
257+
private void addStringAttribute(Map<String, MessageAttributeValue> messageAttributes,
258+
String key, String value) {
259+
MessageAttributeValue messageAttributeValue = new MessageAttributeValue();
260+
messageAttributeValue.setDataType(SQSMessagingClientConstants.STRING);
261+
messageAttributeValue.setStringValue(value);
262+
messageAttributes.put(key, messageAttributeValue);
263+
}
264+
234265
/**
235266
* Sends a message to a queue.
236267
* <P>

src/main/java/com/amazon/sqs/javamessaging/SQSMessagingClientConstants.java

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public class SQSMessagingClientConstants {
2727
public static final int MAX_BATCH = 10;
2828

2929
public static final int MIN_BATCH = 1;
30+
31+
public static final int MIN_PREFETCH = 0;
3032

3133
/**
3234
* JMSMessage available user property types, which are mapped to message

src/main/java/com/amazon/sqs/javamessaging/SQSSessionCallbackScheduler.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ public void run() {
182182
}
183183
} finally {
184184
session.finishedCallback();
185+
186+
// Let the prefetch manager know we're available to
187+
// process another message (if there is a still a listener attached).
188+
messageManager.getPrefetchManager().messageListenerReady();
185189
}
186190
} catch (Throwable ex) {
187191
LOG.error("Unexpected exception thrown during the run of the scheduled callback", ex);
@@ -211,7 +215,7 @@ void scheduleCallBacks(MessageListener messageListener, List<MessageManager> mes
211215
}
212216
}
213217
}
214-
218+
215219
void nackQueuedMessages() {
216220
synchronized (callbackQueue) {
217221
try {

0 commit comments

Comments
 (0)