diff --git a/Makefile b/Makefile index aa0bfc77..b4f2276b 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,8 @@ DEPS_DIR = deps endif endif +export PATH := $(CURDIR):$(CURDIR)/scripts:$(PATH) + MVN_FLAGS += -Ddeps.dir="$(abspath $(DEPS_DIR))" .PHONY: all deps tests clean distclean @@ -33,3 +35,7 @@ distclean: clean $(MAKE) -C $(DEPS_DIR)/rabbitmq_codegen clean .PHONY: cluster-other-node + +.PHONY: doc +doc: ## Generate PerfTest documentation + @mvnw asciidoctor:process-asciidoc diff --git a/jms-client-compliance.md b/jms-client-compliance.md new file mode 100644 index 00000000..73a3b1fb --- /dev/null +++ b/jms-client-compliance.md @@ -0,0 +1,1587 @@ + + +# JMS Client Reference + +This page annotates the [RabbitMQ JMS Client](./jms-client.html) implementation +of the JMS 1.1 API. + +You can download the JMS 1.1 specification and API documentation +from the [Oracle Technology Network Web site](http://www.oracle.com/technetwork/java/docs-136352.html). + +The Compliance Test Suite the JMS Client uses is +[available on GitHub](https://github.com/rabbitmq/rabbitmq-jms-cts). + +## Connection Factory Interfaces + +### ConnectionFactory + + ++++ + + + + + + + + + + +
Connection CreateConnection()
Supported
Connection CreateConnection(java.lang.String userName,
+                            java.lang.String password)
Supported
+ +### QueueConnectionFactory + + ++++ + + + + + + + + + + +
QueueConnection CreateQueueConnection()
Supported
QueueConnection CreateQueueConnection(java.lang.String userName,
+                                      java.lang.String password)
Supported
+ +### TopicConnectionFactory + + ++++ + + + + + + + + + + +
TopicConnection CreateTopicConnection()
Supported
TopicConnection CreateTopicConnection(java.lang.String userName,
+                                      java.lang.String password)
Supported
+ +### XAQueueConnectionFactory + + ++++ + + + + + + + + + + +
XAQueueConnection CreateXAQueueConnection()
Not supported
XAQueueConnection CreateXAQueueConnection(java.lang.String userName,
+                                          java.lang.String password)
Not supported
+ +### XATopicConnectionFactory + + ++++ + + + + + + + + + + +
XATopicConnection CreateXATopicConnection()
Not supported
XATopicConnection CreateXATopicConnection(java.lang.String userName,
+                                          java.lang.String password)
Not supported
+ +## Server Session Interfaces + +The JMS for RabbitMQ client does not support server sessions. + +### ServerSessionPool + + ++++ + + + + + + +
ServerSession getServerSession()
Not supported
+ +### ServerSession + + ++++ + + + + + + + + + + +
Session getSession()
Not supported
void start()
Not supported
+ +## Connection Interfaces + +### Connection + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Session createSession(boolean transacted,
+                      int acknowledgeMode)
Supported
java.lang.String getClientID()
Supported
void setClientID(java.lang.String clientID)
Supported
ConnectionMetaData getMetaData()
Not yet implemented
ExceptionListener getExceptionListener()
Supported
void setExceptionListener(ExceptionListener listener)
Supported
void start()
Supported
void stop()
Supported
void close()
Supported
ConnectionConsumer createConnectionConsumer(Destination destination,
+                                            java.lang.String messageSelector,
+                                            ServerSessionPool sessionPool,
+                                            int maxMessages)
Not supported
ConnectionConsumer createDurableConnectionConsumer(Topic topic,
+                                                   java.lang.String subscriptionName,
+                                                   java.lang.String messageSelector,
+                                                   ServerSessionPool sessionPool,
+                                                   int maxMessages)
Not supported
+ +### QueueConnection + + ++++ + + + + + + + + + + +
QueueSession createQueueSession(boolean transacted,
+                                int acknowledgeMode)
Supported
ConnectionConsumer createConnectionConsumer(Queue queue,
+                                            java.lang.String messageSelector,
+                                            ServerSessionPool sessionPool,
+                                            int maxMessages)
Not supported
+ +### TopicConnection + + ++++ + + + + + + + + + + + + + + +
TopicSession createTopicSession(boolean transacted,
+                                int acknowledgeMode)
Supported
ConnectionConsumer createConnectionConsumer(Topic topic,
+                                            java.lang.String messageSelector,
+                                            ServerSessionPool sessionPool,
+                                            int maxMessages)
Not supported
ConnectionConsumer createDurableConnectionConsumer(Topic topic,
+                                                   java.lang.String subscriptionName,
+                                                   java.lang.String messageSelector,
+                                                   ServerSessionPool sessionPool,
+                                                   int maxMessages)
Not supported
+ +### XAConnection + + ++++ + + + + + + + + + + +
XASession createXASession()
Not yet implemented
Session createSession(boolean transacted,
+                      int acknowledgeMode)
Not yet implemented
+ +### XAQueueConnection + + ++++ + + + + + + + + + + +
XAQueueSession createXAQueueSession()
Not yet implemented
QueueSession createQueueSession(boolean transacted,
+                                int acknowledgeMode)
Not yet implemented
+ +### XATopicConnection + + ++++ + + + + + + + + + + +
XATopicSession createXATopicSession()
Not yet implemented
TopicSession createTopicSession(boolean transacted,
+                                int acknowledgeMode)
Not yet implemented
+ +## Session Interfaces + +### Session + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BytesMessage createBytesMessage()
Supported
MapMessage createMapMessage()
Supported
Message createMessage()
Supported
ObjectMessage createObjectMessage()
Supported
ObjectMessage createObjectMessage(java.io.Serializable object)
Supported
StreamMessage createStreamMessage()
Supported
TextMessage createTextMessage()
Supported
TextMessage createTextMessage(java.lang.String text)
Supported
boolean getTransacted()
Supported
int getAcknowledgeMode()
Supported
void commit()
Supported
void rollback()
Supported
void close()
Supported
void recover()
Supported
MessageListener getMessageListener()
Supported
void setMessageListener(MessageListener listener)
Supported
void run()
Not supported
MessageProducer createProducer(Destination destination)
Supported
MessageConsumer createConsumer(Destination destination)
Supported
MessageConsumer createConsumer(Destination destination,
+                               java.lang.String messageSelector)
Not implemented for non-empty messageSelector
MessageConsumer createConsumer(Destination destination,
+                               java.lang.String messageSelector,
+                               boolean NoLocal)
Not implemented for non-empty messageSelector, and noLocal accepted but ignored
Queue createQueue(java.lang.String queueName)
Supported
Topic createTopic(java.lang.String topicName)
Supported
TopicSubscriber createDurableSubscriber(Topic topic,
+                                        java.lang.String name)
Supported
TopicSubscriber createDurableSubscriber(Topic topic,
+                                        java.lang.String name,
+                                        java.lang.String messageSelector,
+                                        boolean noLocal)
Supported without NoLocal
QueueBrowser createBrowser(Queue queue)
Not yet implemented
QueueBrowser createBrowser(Queue queue,
+                           java.lang.String messageSelector)
Not yet implemented
TemporaryQueue createTemporaryQueue()
Supported
TemporaryTopic createTemporaryTopic()
Supported
void unsubscribe(java.lang.String name)
Supported for durable subscriptions only
+ +### TopicSession + + ++++ + + + + + + + + + + + + + + + + + + +
Topic createTopic(java.lang.String topicName)
Supported
TopicSubscriber createSubscriber(Topic topic,
+                java.lang.String messageSelector,
+                boolean noLocal)
NoLocal is not supported
TopicSubscriber createSubscriber(Topic topic)
Supported
TopicSubscriber createDurableSubscriber(Topic topic,
+                                        java.lang.String name)
Supported
+ +### QueueSession + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Queue createQueue(java.lang.String queueName))
Supported
QueueReceiver createReceiver(Queue queue)
Supported
QueueReceiver createReceiver(Queue queue,
+                             java.lang.String messageSelector)
Not yet implemented
QueueSender createSender(Queue queue)
Supported
QueueBrowser createBrowser(Queue queue)
Supported
QueueBrowser createBrowser(Queue queue,
+                           java.lang.String messageSelector)
Supported
TemporaryQueue createTemporaryQueue()
Supported
+ +### XAQueueSession + + ++++ + + + + + + +
QueueSession getQueueSession()
Not yet implemented
+ +### XASession + + ++++ + + + + + + + + + + + + + + + + + + + + + + +
Session getSession()
Not yet implemented
XAResource getXAResource()
Not yet implemented
boolean getTransacted()
Not yet implemented
void commit()
Not yet implemented
void rollback()
Not yet implemented
+ +### XATopicSession + + ++++ + + + + + + +
TopicSession getTopicSession()
Not yet implemented
+ +## Consumer and Producer Interfaces + +### ConnectionConsumer + + ++++ + + + + + + + + + + +
ServerSessionPool getServerSessionPool()
Not supported
void close()
Not Supported
+ +### MessageProducer + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void setDisableMessageID(boolean value)
Ignored.
boolean getDisableMessageID()
Ignored.
void setDisableMessageTimestamp(boolean value)
Ignored.
boolean getDisableMessageTimestamp()
Ignored.
void setDeliveryMode(int deliveryMode)
Supported
int getDeliveryMode()
Supported
void setPriority(int defaultPriority)
Supported
int getPriority()
Supported
void setTimeToLive(long timeToLive)
Supported
long getTimeToLive()
Supported
void setDeliveryDelay(long deliveryDelay)
Supported
long getDeliveryDelay()
Supported
Destination getDestination()
Supported
void close()
Supported
void send(Message message)
Supported
void send(Message message,
+          int deliveryMode,
+          int priority,
+          long timeToLive)
Supported
void send(Destination destination,
+          Message message)
Supported
void send(Destination destination,
+          Message message,
+          int deliveryMode,
+          int priority,
+          long timeToLive)
Supported
+ +### QueueSender + + ++++ + + + + + + + + + + + + + + + + + + + + + + +
Queue getQueue()
Supported
void send(Message message)
Supported
void send(Message message,
+          int deliveryMode,
+          int priority,
+          long timeToLive)
Supported
void send(Queue queue,
+          Message message)
Supported
void send(Queue queue,
+          Message message,
+          int deliveryMode,
+          int priority,
+          long timeToLive)
Supported
+ +### TopicPublisher + + ++++ + + + + + + + + + + + + + + + + + + + + + + +
Topic getTopic()
Supported
void publish(Message message)
Supported
void publish(Message message,
+             int deliveryMode,
+             int priority,
+             long timeToLive)
Supported
void publish(Topic topic,
+             Message message)
Supported
void publish(Topic topic,
+             Message message,
+             int deliveryMode,
+             int priority,
+             long timeToLive)
Supported
+ +## Message Interfaces + +### Message + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
java.lang.String getJMSMessageID()
Supported
void setJMSMessageID(java.lang.String id)
Supported
long getJMSTimestamp()
Supported
void setJMSTimestamp(long timestamp)
Supported
byte[] getJMSCorrelationIDAsBytes()
Supported
void setJMSCorrelationIDAsBytes(byte[] correlationID)
Supported
void setJMSCorrelationID(java.lang.String correlationID)
Supported
java.lang.String getJMSCorrelationID()
Supported
Destination getJMSReplyTo()
Supported
void setJMSReplyTo(Destination replyTo)
Supported
Destination getJMSDestination()
Supported
void setJMSDestination(Destination destination)
Supported
int getJMSDeliveryMode()
Supported
void setJMSDeliveryMode(int deliveryMode)
Supported
boolean getJMSRedelivered()
Supported
void setJMSRedelivered(boolean redelivered)
Supported
java.lang.String getJMSType()
Supported
void setJMSType(java.lang.String type)
Supported
long getJMSExpiration()
Supported
void setJMSExpiration(long expiration)
Supported
long getJMSDeliveryTime()
Supported
void setJMSDeliveryTime(long deliveryTime)
Supported
int getJMSPriority()
Supported
void setJMSPriority(int priority)
Supported
void clearProperties()
Supported
boolean propertyExists(java.lang.String name)
Supported
boolean getBooleanProperty(java.lang.String name)
Supported
byte getByteProperty(java.lang.String name)
Supported
short getShortProperty(java.lang.String name)
Supported
int getIntProperty(java.lang.String name)
Supported
long getLongProperty(java.lang.String name)
Supported
float getFloatProperty(java.lang.String name)
Supported
double getDoubleProperty(java.lang.String name)
Supported
java.lang.String getStringProperty(java.lang.String name)
Supported
java.lang.Object getObjectProperty(java.lang.String name)
Supported
java.util.Enumeration getPropertyNames()
Supported
void setBooleanProperty(java.lang.String name,
+                        boolean value)
Supported
void setShortProperty(java.lang.String name,
+                      short value)
Supported
void setIntProperty(java.lang.String name,
+                    int value)
Supported
void setLongProperty(java.lang.String name,
+                     long value)
Supported
void setFloatProperty(java.lang.String name,
+                      float value)
Supported
void setDoubleProperty(java.lang.String name,
+                       double value)
Supported
void setStringProperty(java.lang.String name,
+                       java.lang.String value)
Supported
void setObjectProperty(java.lang.String name,
+                       java.lang.Object value)
Supported
void acknowledge()
Supported
void clearBody()
Supported
+ +### BytesMessage + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
long getBodyLength()
Supported
boolean readBoolean()
Supported
byte readByte()
Supported
int readUnsignedByte()
Supported
short readShort()
Supported
int readUnsignedShort()
Supported
char readChar()
Supported
int readInt()
Supported
long readLong()
Supported
float readFloat()
Supported
double readDouble()
Supported
java.lang.String readUTF()
Supported
int readBytes(byte[] value)
Supported
int readBytes(byte[] value,
+              int length)
Supported
void writeBoolean(boolean value)
Supported
void writeByte(byte value)
Supported
void writeShort(short value)
Supported
void writeChar(char value)
Supported
void writeInt(int value)
Supported
void writeLong(long value)
Supported
void writeFloat(float value)
Supported
void writeDouble(double value)
Supported
void writeUTF(java.lang.String value)
Supported
void writeBytes(byte[] value)
Supported
void writeBytes(byte[] value,
+                int offset,
+                int length)
Supported
void writeObject(java.lang.Object value)
Supported
void reset()
Supported
+ +### MapMessage + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
boolean getBoolean(java.lang.String name)
Supported
byte getByte(java.lang.String name)
Supported
short getShort(java.lang.String name)
Supported
char getChar(java.lang.String name)
Supported
int getInt(java.lang.String name)
Supported
long getLong(java.lang.String name)
Supported
float getFloat(java.lang.String name)
Supported
double getDouble(java.lang.String name)
Supported
java.lang.String getString(java.lang.String name)
Supported
byte[] getBytes(java.lang.String name)
Supported
java.lang.Object getObject(java.lang.String name)
Supported
java.util.Enumeration getMapNames()
Supported
void setBoolean(java.lang.String name,
+                boolean value)
Supported
void setByte(java.lang.String name,
+             byte value)
Supported
void setShort(java.lang.String name,
+              short value)
Supported
void setChar(java.lang.String name,
+             char value)
Supported
void setInt(java.lang.String name,
+            int value)
Supported
void setLong(java.lang.String name,
+             long value)
Supported
void setFloat(java.lang.String name,
+              float value)
Supported
void setDouble(java.lang.String name,
+               double value)
Supported
void setString(java.lang.String name,
+               java.lang.String value)
Supported
void setBytes(java.lang.String name,
+              byte[] value)
Supported
void setBytes(java.lang.String name,
+              byte[] value,
+              int offset,
+              int length)
Supported
void setObject(java.lang.String name,
+               java.lang.Object value)
Supported
boolean itemExists(java.lang.String name)
Supported
+ +### ObjectMessage + + ++++ + + + + + + + + + + +
void setObject(java.io.Serializable object)
Supported
java.io.Serializable getObject()
Supported
+ +### StreamMessage + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
boolean readBoolean()
Supported
byte readByte()
Supported
short readShort()
Supported
char readChar()
Supported
int readInt()
Supported
long readLong()
Supported
float readFloat()
Supported
double readDouble()
Supported
java.lang.String readString()
Supported
int readBytes(byte[] value)
Supported
java.lang.Object readObject()
Supported
void writeBoolean(boolean value)
Supported
oid writeByte(byte value)
Supported
void writeShort(short value)
Supported
void writeChar(char value)
Supported
void writeInt(int value)
Supported
void writeLong(long value)
Supported
void writeFloat(float value)
Supported
void writeDouble(double value)
Supported
void writeString(java.lang.String value)
Supported
void writeBytes(byte[] value)
Supported
void writeBytes(byte[] value,
+                int offset,
+                int length)
Supported
void writeObject(java.lang.Object value)
Supported
void reset()
Supported
+ +### TextMessage + + ++++ + + + + + + + + + + +
void setText(java.lang.String string)
Supported
java.lang.String getText()
Supported
+ +## Message Consumer Interfaces + +### MessageConsumer + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
java.lang.String getMessageSelector()
Supported
MessageListener getMessageListener()
Supported
void setMessageListener(MessageListener listener)
Supported
Message receive()
Supported
Message receive(long timeout)
Supported
Message receiveNoWait()
Supported
void close()
Supported
+ +### QueueReceiver + + ++++ + + + + + + +
Queue getQueue()
Supported
+ +### TopicSubscriber + + ++++ + + + + + + + + + + +
Topic getTopic()
Supported
boolean getNoLocal()
NoLocal is not supported
+ +## Destination Interfaces + +### Destination + +(Has No Methods) + +### Queue + + ++++ + + + + + + + + + + +
java.lang.String getQueueName()
Supported
java.lang.String toString()
Supported
+ +### TemporaryQueue + + ++++ + + + + + + +
void delete()
Supported
+ +### Topic + + ++++ + + + + + + + + + + +
java.lang.String getTopicName()
Supported
java.lang.String toString()
Supported
+ +### TemporaryTopic + + ++++ + + + + + + +
void delete()
Supported
+ +## QueueBrowser + +See [QueueBrowser support](jms-client.html#queue-browser-support) for implementation details. + + ++++ + + + + + + + + + + + + + + + + + + +
Queue getQueue()
Supported
java.lang.String getMessageSelector()
Supported
java.util.Enumeration getEnumeration()
Supported
void close()
Supported
diff --git a/pom.xml b/pom.xml index 1c34b55c..109bafc6 100644 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,8 @@ 1.6.13 1.11 3.3.0 + 2.2.2 + 2.5.7 11 UTF-8 @@ -277,6 +279,40 @@ + + org.asciidoctor + asciidoctor-maven-plugin + ${asciidoctor.maven.plugin.version} + + + org.asciidoctor + asciidoctorj + ${asciidoctorj.version} + + + + src/docs/asciidoc + index.adoc + + html5 + + ${project.build.sourceDirectory} + ${project.version} + ${spring.version} + ./images + left + font + true + + + - + true + coderay + + + + + @@ -438,17 +474,6 @@ false - - spring-milestone - Spring Milestone Repository - https://repo.spring.io/milestone - - true - - - false - - diff --git a/publish-documentation-to-github-pages.sh b/publish-documentation-to-github-pages.sh new file mode 100755 index 00000000..409172b3 --- /dev/null +++ b/publish-documentation-to-github-pages.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +. $(pwd)/release-versions.txt + +MESSAGE=$(git log -1 --pretty=%B) + +git checkout -- .mvn/maven.config +RELEASE_VERSION=`./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout` +CURRENT_BRANCH=`git rev-parse --abbrev-ref HEAD` + +git remote set-branches origin 'gh-pages' +git fetch -v + +git checkout gh-pages +mkdir -p ${API_FAMILY}/${RELEASE_VERSION}/htmlsingle +cp target/generated-docs/* ${API_FAMILY}/${RELEASE_VERSION}/htmlsingle +git add ${API_FAMILY}/${RELEASE_VERSION}/ + +if [[ $LATEST == "true" ]] +then + if [[ $RELEASE_VERSION == *[RCM]* ]] + then + DOC_DIR="milestone" + elif [[ $RELEASE_VERSION == *SNAPSHOT* ]] + then + DOC_DIR="snapshot" + else + DOC_DIR="stable" + fi + + mkdir -p ${API_FAMILY}/${DOC_DIR}/htmlsingle + cp target/generated-docs/* ${API_FAMILY}/${DOC_DIR}/htmlsingle + git add ${API_FAMILY}/${DOC_DIR}/ +fi + +git commit -m "$MESSAGE" +git push origin gh-pages +git checkout $CURRENT_BRANCH diff --git a/release-versions.txt b/release-versions.txt index d17a5175..a0383d6f 100644 --- a/release-versions.txt +++ b/release-versions.txt @@ -1,3 +1,5 @@ -RELEASE_VERSION="3.0.0.RC1" +RELEASE_VERSION="3.1.0.RC1" DEVELOPMENT_VERSION="3.1.0-SNAPSHOT" RELEASE_BRANCH="main" +API_FAMILY="3.x" +LATEST=true diff --git a/src/docs/asciidoc/implementation-details.adoc b/src/docs/asciidoc/implementation-details.adoc new file mode 100644 index 00000000..a7979282 --- /dev/null +++ b/src/docs/asciidoc/implementation-details.adoc @@ -0,0 +1,192 @@ + +== Implementation Details + +This section provides additional implementation details for specific +JMS API classes in the JMS Client. + +Deviations from the specification are implemented to support common +acknowledgement behaviours. + +=== JMS Topic Support + +JMS topics are implemented using an AMQP link:https://rabbitmq.com/tutorials/amqp-concepts.html#exchange-topic[topic exchange] +and a dedicated AMQP queue for each JMS topic subscriber. The AMQP +topic exchange is `jms.temp.topic` or `jms.durable.topic`, depending +on whether the JMS topic is temporary or not, respectively. Let's +take an example with a subscription to a durable `my.jms.topic` JMS topic: + +* a dedicated AMQP queue is created for this subscriber, its name + will follow the pattern `+jms-cons-{UUID}+`. +* the `+jms-cons-{UUID}+` AMQP queue is bound to the `jms.durable.topic` + exchange with the `my.jms.topic` binding key. + +If another subscriber subscribes to `my.jms.topic`, it will have +its own AMQP queue and both subscribers will receive messages published +to the `jms.durable.topic` exchange with the `my.jms.topic` routing key. + +The example above assumes no topic selector is used when declaring the +subscribers. If a topic selector is in use, a `x-jms-topic`-typed exchange +will sit between the `jms.durable.topic` topic exchange and the +subscriber queue. So the topology is the following when subscribing to +a durable `my.jms.topic` JMS topic with a selector: + +* a dedicated AMQP queue is created for this subscriber, its name + will follow the pattern `+jms-cons-{UUID}+`. +* a `x-jms-topic`-typed exchange is bound to the subscriber AMQP queue with + the `my.jms.topic` binding key and some arguments related to the selector + expressions. Note this exchange is scoped to the JMS session and not only + to the subscriber. +* the `x-jms-topic`-typed exchange is bound to the `jms.durable.topic` + exchange with the `my.jms.topic` binding key. + +Exchanges can be bound together thanks to a link:https://rabbitmq.com/e2e.html[RabbitMQ extension]. +Note the <> must be enabled for topic selectors +to work. + +=== QueueBrowser Support + +==== Overview of queue browsers + +The JMS API includes objects and methods to browse an existing queue +destination, reading its messages _without_ removing them from the +queue. Topic destinations cannot be browsed in this manner. + +A `QueueBrowser` can be created from a (queue) `Destination`, +with or without a selector expression. The browser has a `getEnumeration()` +method, which returns a Java `Enumeration` of ``Message``s copied from +the queue. + +If no selector is supplied, then all messages in the queue appear +in the `Enumeration`. If a selector is supplied, then only those +messages that satisfy the selector appear. + +==== Implementation + +The destination queue is read when the `getEnumeration()` method is +called. A _snapshot_ is taken of the messages in the queue; and the +selector expression, if one is supplied, is used at this time to discard +messages that do not match. + +The message copies may now be read using the `Enumeration` interface +(`nextElement()` and `hasMoreElements()`). + +The selector expression and the destination queue of the `QueueBrowser` +may not be adjusted after the `QueueBrowser` is created. + +An `Enumeration` cannot be "reset", but the `getEnumeration()` method +may be re-issued, taking a _new_ snapshot from the queue each time. + +The contents of an `Enumeration` survive session and/or connection +close, but a `QueueBrowser` may not be used after the session that +created it has closed. `QueueBrowser.close()` has no effect. + +===== Which messages are included + +Messages that arrive, expire, are re-queued, or are removed after +the `getEnumeration()` call have no effect on the contents of the +`Enumeration` it produced. If the messages in the queue change +_while the_ `Enumeration` _is being built_, they may or may not be +included. In particular, if messages from the queue are simultaneously +read by another client (or session), they may or may not appear in +the `Enumeration`. + +Message copies do not "expire" from an `Enumeration`. + +===== Order of messages + +If other client sessions read from a queue that is being browsed, +then it is possible that some messages may subsequently be received out +of order. + +Message order will not be disturbed if no other client sessions read +the queue at the same time. + +===== Memory usage + +When a message is read from the `Enumeration` (with `nextElement()`), +then no reference to it is retained in the Java Client. This means the +storage it occupies in the client is eligible for release +(by garbage collection) if no other references are retained. +Retaining an `Enumeration` will retain the storage for all message +copies that remain in it. + +If the queue has many messages -- or the messages it contains are very +large -- then a `getEnumeration()` method call may consume a large +amount of memory in a very short time. This remains true even if only +a few messages are selected. There is currently limited protection +against `OutOfMemoryError` conditions that may arise because of this. +See the next section. + +===== Setting a maximum number of messages to browse + +Each connection is created with a limit on the number of messages that +are examined by a `QueueBrowser`. The limit is set on the +`RMQConnectionFactory` by `RMQConnectionFactory.setQueueBrowserReadMax(int)` +and is passed to each `Connection` subsequently created +by `ConnectionFactory.createConnection()`. + +The limit is an integer that, if positive, stops the queue browser from +reading more than this number of messages when building an enumeration. +If it is zero or negative, it is interpreted as imposing no limit on +the browser, and all of the messages on the queue are scanned. + +The default limit for a factory is determined by the +`rabbit.jms.queueBrowserReadMax` system property, if set, and the value +is specified as `0` if this property is not set or is not an integer. + +If a `RMQConnectionFactory` value is obtained from a JNDI provider, +then the limit set when the factory object was created is preserved. + +===== Release Support + +Support for ``QueueBrowser``s is introduced in the JMS Client 1.2.0. +Prior to that release, calling `Session.createBrowser(Queue queue[, String selector])` +resulted in an `UnsupportedOperationException`. + +=== Group and individual acknowledgement + +Prior to version 1.2.0 of the JMS client, in client acknowledgement mode +(`Session.CLIENT_ACKNOWLEDGE`), acknowledging any message from an open +session would acknowledge _every_ unacknowledged message of that session, +whether they were received before or after the message being acknowledged. + +Currently, the behaviour of `Session.CLIENT_ACKNOWLEDGE` mode is +modified so that, when calling `msg.acknowledge()`, only the message +`msg` _and all_ previously received _unacknowledged messages on that +session_ are acknowledged. Messages received _after_ `msg` was received +are not affected. This is a form of _group acknowledgement_, +which differs slightly from the JMS specification but is likely to +be more useful, and is compatible with the vast majority of uses of +the existing acknowledge function. + +For even finer control, a new acknowledgement mode may be set when +creating a session, called `RMQSession.CLIENT_INDIVIDUAL_ACKNOWLEDGE`. + +A session created with this acknowledgement mode will mean that messages +received on that session will be acknowledged individually. That is, +the call `msg.acknowledge()` will acknowledge only the message `msg` +and not affect any other messages of that session. + +The acknowledgement mode `RMQSession.CLIENT_INDIVIDUAL_ACKNOWLEDGE` +is equivalent to `Session.CLIENT_ACKNOWLEDGE` in all other respects. +In particular the `getAcknowledgeMode()` method returns +`Session.CLIENT_ACKNOWLEDGE` even if +`RMQSession.CLIENT_INDIVIDUAL_ACKNOWLEDGE` has been set. + +=== Arbitrary Message support + +Any instance of a class that implements the `javax.jms.Message` +interface can be _sent_ by a JMS message producer. + +All properties of the message required by `send()` are correctly +interpreted except that the `JMSReplyTo` header and objects +(as property values or the body of an `ObjectMessage`) that +cannot be deserialized are ignored. + +The implementation extracts the properties and body from the `Message` +instance using interface methods and recreates it as a message of +the right (`RMQMessage`) type (`BytesMessage`, `MapMessage`, `ObjectMessage`, +`TextMessage`, or `StreamMessage`) before sending it. This means +that there is some performance loss due to the copying; but in the +normal case, when the message is an instance of +`com.rabbitmq.jms.client.RMQMessage`, no copying is done. diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc new file mode 100644 index 00000000..cd695b10 --- /dev/null +++ b/src/docs/asciidoc/index.adoc @@ -0,0 +1,97 @@ += RabbitMQ JMS Client +:revnumber: {project-version} +:example-caption!: +ifndef::imagesdir[:imagesdir: images] +ifndef::sourcedir[:sourcedir: ../../main/java] +:source-highlighter: prettify + +== Introduction + +RabbitMQ is not a JMS provider but includes https://github.com/rabbitmq/rabbitmq-server/tree/main/deps/rabbitmq_jms_topic_exchange[a plugin] +needed to support the JMS Queue and Topic messaging models. JMS Client +for RabbitMQ implements the JMS specification on top of the +link:https://rabbitmq.com/api-guide.html[RabbitMQ Java client], thus allowing new and +existing JMS applications to connect to RabbitMQ. + +The library supports JMS 2.0 as of 2.7.0 and JMS 3.0 as of 3.0.0. + +The plugin and the JMS client are meant to work and be used together. + +See the link:https://rabbitmq.com/java-versions.html[RabbitMQ Java libraries support page] for the support timeline +of the RabbitMQ JMS Client library. + + +== Components + +To fully leverage JMS with RabbitMQ, you need the following components: + +* the https://github.com/rabbitmq/rabbitmq-jms-client[JMS client library] and its dependent libraries. +* https://github.com/rabbitmq/rabbitmq-server/tree/main/deps/rabbitmq_jms_topic_exchange[RabbitMQ JMS topic selector plugin] that is included +with RabbitMQ starting with version 3.6.3. To support message selectors for JMS +topics, the RabbitMQ Topic Selector plugin must be installed on the +RabbitMQ server. Message selectors allow a JMS application to filter +messages using an expression based on SQL syntax. Message selectors +for Queues are not currently supported. + +== JMS and AMQP 0-9-1 + +JMS is the standard messaging API for the JEE platform. It is +available in commercial and open source implementations. Each +implementation includes a JMS provider, a JMS client library, and +additional, implementation-specific components for administering the +messaging system. The JMS provider can be a standalone implementation +of the messaging service, or a bridge to a non-JMS messaging system. + +The JMS client API is standardized, so JMS applications are portable +between vendors`' implementations. However, the underlying messaging +implementation is unspecified, so there is no interoperability between +JMS implementations. Java applications that want to share messaging +must all use the same JMS implementation unless bridging technology +exists. Furthermore, non-Java applications cannot access JMS without a +vendor-specific JMS client library to enable interoperability. + +AMQP 0-9-1 is a messaging protocol, rather than an API like JMS. Any +client that implements the protocol can access a broker that supports +AMQP 0-9-1. Protocol-level interoperability allows AMQP 0-9-1 clients +written in any programming language and running on any operating +system to participate in the messaging system with no need to bridge +incompatible vendor implementations. + +Because JMS Client for RabbitMQ is implemented using the RabbitMQ Java +client, it is compliant with both the JMS API and the AMQP 0-9-1 protocol. + +You can download the JMS 2.0 specification and API documentation from +the https://download.oracle.com/otndocs/jcp/jms-2_0_rev_a-mrel-eval-spec/index.html[Oracle Technology Network Web site]. + +== Limitations + +Some JMS 1.1 and 2.0 features are unsupported in the RabbitMQ JMS Client: + +* The JMS Client does not support server sessions. +* XA transaction support interfaces are not implemented. +* Topic selectors are supported with the RabbitMQ JMS topic selector + plugin. Queue selectors are not yet implemented. +* SSL and socket options for RabbitMQ connections are supported, but + only using the (default) SSL connection protocols that the RabbitMQ client provides. +* The JMS `NoLocal` subscription feature, which prevents delivery of + messages published from a subscriber's own connection, is not supported + with RabbitMQ. You can call a method that includes the `NoLocal` + argument, but it is ignored. + +See link:https://github.com/rabbitmq/rabbitmq-jms-client/blob/main/jms-client-compliance.md[the JMS API compliance documentation] for a +detailed list of supported JMS APIs. + +include::installation.adoc[] +include::interoperability.adoc[] +include::logging.adoc[] +include::publisher-confirm.adoc[] +include::rpc.adoc[] +include::implementation-details.adoc[] + +== Further Reading + +To gain better understanding of AMQP 0-9-1 concepts and interoperability of +the RabbitMQ JMS client with AMQP 0-9-1 clients, you may wish to read an +link:https://rabbitmq.com/tutorials/amqp-concepts.html[Introduction to RabbitMQ Concepts] +and browse our +link:https://rabbitmq.com/amqp-0-9-1-quickref.html[AMQP 0-9-1 Quick Reference Guide]. diff --git a/src/docs/asciidoc/installation.adoc b/src/docs/asciidoc/installation.adoc new file mode 100644 index 00000000..531812ec --- /dev/null +++ b/src/docs/asciidoc/installation.adoc @@ -0,0 +1,309 @@ +== Installing and Configuring + +=== Enabling the Topic Selector Plug-in[[enable_topic_selector]] + +The topic selector plugin is included with RabbitMQ. Like any RabbitMQ +plugin, you need to enable the plugin in order to use it. + +Enable the plugin using the `rabbitmq-plugins` command: + + rabbitmq-plugins enable rabbitmq_jms_topic_exchange + + +You don't need to restart the broker to activate the plugin. + +[NOTE] +==== +You need to enable this plugin only if you plan to use topic selectors +in your JMS client applications +==== + +=== Installing JMS Client library + +Use your favorite build management tool to add the client dependencies to your project. + +==== Maven + +.pom.xml +[source,xml,subs="attributes,specialcharacters"] +---- + + + + com.rabbitmq.jms + rabbitmq-jms + {project-version} + + + +---- + +Milestones and release candidates require to declare the <>. +Snapshots require to declare their <> as well. + +==== Gradle + +.build.gradle +[source,groovy,subs="attributes,specialcharacters"] +---- +dependencies { + compile "com.rabbitmq.jms:rabbitmq-jms:{project-version}" +} +---- + +Milestones and release candidates require to declare the <>. +Snapshots require to declare their <> as well. + +[[milestone-rc-repository]] +==== Milestones and Release Candidates + +Releases are available from Maven Central, which does not require specific declaration. +Milestones and release candidates are available from a repository that must be declared in the dependency management configuration. + +With Maven: + +.Milestone and release candidate repository declaration for Maven +[source,xml,subs="attributes,specialcharacters"] +---- + + + + packagecloud-rabbitmq-maven-milestones + https://packagecloud.io/rabbitmq/maven-milestones/maven2 + + true + + + false + + + + +---- + +With Gradle: + +.Milestone and release candidate repository declaration for Gradle: +[source,groovy,subs="attributes,specialcharacters"] +---- +repositories { + maven { url "https://packagecloud.io/rabbitmq/maven-milestones/maven2" } + mavenCentral() +} +---- + +[[snapshot-repository]] +==== Snapshots + +Releases are available from Maven Central, which does not require specific declaration. +Snapshots are available from a repository that must be declared in the dependency management configuration. + +With Maven: + +.Snapshot repository declaration for Maven +[source,xml,subs="attributes,specialcharacters"] +---- + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + true + false + + + +---- + +With Gradle: + +.Snapshot repository declaration for Gradle: +[source,groovy,subs="attributes,specialcharacters"] +---- +repositories { + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + mavenCentral() +} +---- + +=== Enabling the JMS client in a Java container + +To enable the JMS Client in a Java container (e.g. Java EE application +server, web container), you must install the JMS client JAR files and +its dependencies in the container and then define JMS resources in +the container's naming system so that JMS clients can look them up. +The methods for accomplishing these tasks are container-specific, please +refer to the vendors`' documentation. + +For standalone applications, you need to add the JMS client JAR files +and its dependencies to the application classpath. The JMS resources +can be defined programmatically or through a dependency injection +framework like Spring. + +=== Defining the JMS Connection Factory + +To define the JMS `ConnectionFactory` in JNDI, e.g. in Tomcat: + +[source,xml] +-------- + +-------- + +To define the JMS `ConnectionFactory` in JNDI, e.g. in WildFly (as of JMS Client 1.7.0): + +[source,xml] +-------- + + + + + + + + + + +-------- + +Here is the equivalent Spring bean example (Java configuration): +[source,java,indent=0] +---- +@Bean +public ConnectionFactory jmsConnectionFactory() { + RMQConnectionFactory connectionFactory = new RMQConnectionFactory(); + connectionFactory.setUsername("guest"); + connectionFactory.setPassword("guest"); + connectionFactory.setVirtualHost("/"); + connectionFactory.setHost("localhost"); + connectionFactory.setPort(5672); + return connectionFactory; +} +---- + +And here is the Spring XML configuration: + +[source,xml] +-------- + + + + + + + +-------- + +The following table lists all of the attributes/properties that are available. + +|=== +| Attribute/Property | JNDI only? | Description | + +| `name` +| Yes +| Name in JNDI. +| + +| `type` +| Yes +| Name of the JMS interface the object implements, usually `javax.jms.ConnectionFactory`. Other choices are `javax.jms.QueueConnectionFactory` and `javax.jms.TopicConnectionFactory`. You can also use the name of the (common) implementation class, `com.rabbitmq.jms.admin.RMQConnectionFactory`. +| + +| `factory` +| Yes +| JMS Client for RabbitMQ `ObjectFactory` class, always `com.rabbitmq.jms.admin.RMQObjectFactory`. +| + +| `username` +| No +| Name to use to authenticate a connection with the RabbitMQ broker. The default is "guest". +| + +| `password` +| No +| Password to use to authenticate a connection with the RabbitMQ broker. The default is "guest". +| + +| `virtualHost` +| No +| RabbitMQ https://www.rabbitmq.com/vhosts.html[virtual host] within which the application will operate. The default is "/". +| + +| `host` +| No +| Host on which RabbitMQ is running. The default is "localhost". +| + +| `port` +| No +| RabbitMQ port used for connections. The default is "5672" unless this is a link:https://rabbitmq.com/ssl.html[TLS connection], in which case the default is "5671". +| + +| `ssl` +| No +| Whether to use an SSL connection to RabbitMQ. The default is "false". See the `useSslProtocol` methods for more information. +| + +| `uri` +| No +| The link:https://rabbitmq.com/uri-spec.html[AMQP 0-9-1 URI] string used to establish a RabbitMQ connection. The value can encode the `host`, `port`, `username`, `password` and `virtualHost` in a single string. Both 'amqp' and 'amqps' schemes are accepted. Note: this property sets other properties and the set order is unspecified. +| + +| `uris` +| No +| A list of link:https://rabbitmq.com/uri-spec.html[AMQP 0-9-1 URI] strings to establish a connection to one of the nodes of a RabbitMQ cluster. Each URI is processed in the same way as the `uri` property (`host`, `port`, `username`, etc). This has the same effect as specifying a https://rabbitmq.com/api-guide.html#endpoints-list[list of endpoints in the AMQP client]. The property is a `List` in the `RMQConnectionFactory` and a `String` expecting a list of comma-separated URIs in the `RMQObjectFactory` (JNDI). Note: this property sets other properties and the set order is unspecified. +| + +| `onMessageTimeoutMs` +| No +| How long to wait for `MessageListener#onMessage()` to return, in milliseconds. Default is 2000 ms. +| + +| `preferProducerMessageProperty` +| No +| Whether `MessageProducer` properties (delivery mode, priority, TTL) take precedence over respective `Message` properties or not. Default is true (which is compliant to the JMS specification). +| + +| `requeueOnMessageListenerException` +| No +| Whether requeuing messages on a `RuntimeException` in the `MessageListener` or not. Default is false. +| + +| `queueBrowserReadMax` +| No +| The maximum number of messages to read on a queue browser. Default is 0 (no limit). +| + +| `onMessageTimeoutMs` +| No +| The time in milliseconds `MessageListener#onMessage(Message)` can take to process a message. Default is 2000 ms. +| + +| `channelsQos` +| No +| https://www.rabbitmq.com/consumer-prefetch.html[QoS setting] for channels created by the connection factory. Default is -1 (no QoS). +| + +| `terminationTimeout` +| No +| The time in milliseconds a `Connection#close()` should wait for threads/tasks/listeners to complete. Default is 15,000 ms. +| + +| `declareReplyToDestination` +| No +| Whether `replyTo` destination for consumed messages should be declared. Default is true. +| + +| `keepTextMessageType` +| No +| When set to `true`, the AMQP `JMSType` header will be set automatically to `"TextMessage"` for ``TextMessage``s published to AMQP-backed ``Destination``s. Default is false. +| +|=== diff --git a/src/docs/asciidoc/interoperability.adoc b/src/docs/asciidoc/interoperability.adoc new file mode 100644 index 00000000..210faba8 --- /dev/null +++ b/src/docs/asciidoc/interoperability.adoc @@ -0,0 +1,182 @@ +== JMS and AMQP 0-9-1 Destination Interoperability[[destination-interoperability]] + +An interoperability feature allows you to define JMS 'amqp' destinations +that read and/or write to non-JMS RabbitMQ resources. *_Note this feature +does not support JMS topics_*. + +A single 'amqp' destination can be defined for both sending and consuming. + +=== Sending JMS Messages to an AMQP Exchange + +A JMS destination can be defined so that a JMS application can send +``Message``s to a predefined RabbitMQ 'destination' (exchange/routing key) +using the JMS API in the normal way. The messages are written +"in the clear," which means that any AMQP 0-9-1 client can read them without +having to understand the internal format of Java JMS messages. +*_Only ``BytesMessage``s and ``TextMessage``s can be written in this way_*. + +When messages are sent to an 'amqp' Destination, JMS message properties +are mapped onto AMQP 0-9-1 headers and properties as appropriate. +For example, the `JMSPriority` property converts to the `priority` property +for the AMQP 0-9-1 message. (It is also set as a header with the name +"JMSPriority".) User-defined properties are set as named message header +values, provided they are `boolean`, numeric or `String` types. + +=== Consuming Messages From an AMQP Queue + +Similarly, a JMS destination can be defined that reads messages from a +predefined RabbitMQ queue. A JMS application can then read these +messages using the JMS API. RabbitMQ JMS Client packs them up into +JMS Messages automatically. Messages read in this way are, by default, +``BytesMessage``s, but individual messages can be marked `TextMessage` +(by adding an AMQP message header called "JMSType" whose value is +"TextMessage"), which will interpret the byte-array payload as a UTF8 +encoded String and return them as ``TextMessage``s. + +When reading from an 'amqp' Destination, values are mapped back to +JMS message properties, except that any explicit JMS property set as +a message header overrides the natural AMQP 0-9-1 header value, unless +this would misrepresent the message. For example, +`JMSDeliveryMode` cannot be overridden in this way. + +=== JMS 'amqp' RMQDestination Constructor + +The `com.rabbitmq.jms.admin` package contains the `RMQDestination` class, +which implements `Destination` in the JMS interface. This is extended +with a new constructor: + +[source,java,indent=0] +---- + public RMQDestination(String destinationName, String amqpExchangeName, + String amqpRoutingKey, String amqpQueueName); +---- + +This constructor creates a destination for JMS for RabbitMQ mapped +onto an AMQP 0-9-1 resource. The parameters are the following: + +* `destinationName` - the name of the queue destination +* `amqpExchangeName` - the exchange name for the mapped resource +* `amqpRoutingKey` - the routing key for the mapped resource +* `amqpQueueName` - the queue name of the mapped resource (to listen + messages from) + +Applications that declare destinations in this way can use them directly, +or store them in a JNDI provider for JMS applications to retrieve. +Such destinations are non-temporary, queue destinations. + +=== JMS AMQP 0-9-1 Destination Definitions + +The `RMQDestination` object has the following new instance fields: + +* `amqp` -- _boolean_, indicates if this is an AMQP 0-9-1 destination + (if *true*); the default is *false*. +* `amqpExchangeName` -- _String_, the RabbitMQ exchange name to use when + sending messages to this destination, if `amqp` is *true*; the default + is *null*. +* `amqpRoutingKey` -- _String_, the AMQP 0-9-1 routing key to use when sending + messages to this destination, if `amqp` is *true*; the default is *null*. +* `amqpQueueName` -- _String_, the RabbitMQ queue name to use when reading + messages from this destination, if `amqp` is *true*; the default is *null*. + +There are getters and setters for these fields, which means that a JNDI + `` definition or an XML Spring bean definition can use them, for example + JNDI with Tomcat: + +[source,xml] +---- + + +---- + +This is the equivalent with WildFly (as of JMS Client 1.7.0): + +[source,xml] +---- + + + + + + + + + + +---- + + +This is the equivalent Spring bean example (Java configuration): + +[source,java,indent=0] +---- + @Bean + public Destination jmsDestination() { + RMQDestination jmsDestination = new RMQDestination(); + jmsDestination.setDestinationName("myQueue"); + jmsDestination.setAmqp(true); + jmsDestination.setAmqpQueueName("rabbitQueueName"); + return jmsDestination; + } +---- + +And here is the Spring XML configuration: + +[source,xml] +---- + + + + + +---- + +Following is a _complete_ list of the attributes/properties that are +available: + +|=== +| Attribute/Property Name | JNDI Only? | Description + +| `name` +| Yes +| Name in JNDI. + +| `type` +| Yes +| Name of the JMS interface the object implements, usually `javax.jms.Queue`. Other choices are `javax.jms.Topic` and `javax.jms.Destination`. You can also use the name of the (common) implementation class, `com.rabbitmq.jms.admin.RMQDestination`. + +| `factory` +| Yes +| JMS Client for RabbitMQ `ObjectFactory` class, always `com.rabbitmq.jms.admin.RMQObjectFactory`. + +| `amqp` +| No +| "*true*" means this is an 'amqp' destination. Default "*false*". + +| `amqpExchangeName` +| No +| Name of the RabbitMQ exchange to publish messages to when an 'amqp' destination. This exchange must exist when messages are published. + +| `amqpRoutingKey` +| No +| The routing key to use when publishing messages when an 'amqp' destination. + +| `amqpQueueName` +| No +| Name of the RabbitMQ queue to receive messages from when an 'amqp' destination. This queue must exist when messages are received. + +| `destinationName` +| No +| Name of the JMS destination. + +| `queueDeclareArguments` +| No +| Arguments to use when declaring the AMQP queue. Use `key=value` pairs separated by commas for JNDI, e.g. `x-queue-type=quorum`. +|=== diff --git a/src/docs/asciidoc/logging.adoc b/src/docs/asciidoc/logging.adoc new file mode 100644 index 00000000..d5a0ccbd --- /dev/null +++ b/src/docs/asciidoc/logging.adoc @@ -0,0 +1,11 @@ +== Configuring Logging for the JMS Client + +The JMS Client logs messages using SLF4J (Simple Logging Façade for Java). +SLF4J delegates to a logging framework, such as https://logback.qos.ch/[Logback]. +If no other logging framework is +enabled, SLF4J defaults to a built-in, no-op, logger. +See the http://www.slf4j.org/docs.html[SLF4J] documentation for a +list of the logging frameworks SLF4J supports. + +We highly recommend to use a dependency management tool like http://maven.apache.org/[Maven] +or https://gradle.org/[Gradle] to manage dependencies. diff --git a/src/docs/asciidoc/publisher-confirm.adoc b/src/docs/asciidoc/publisher-confirm.adoc new file mode 100644 index 00000000..2c03668a --- /dev/null +++ b/src/docs/asciidoc/publisher-confirm.adoc @@ -0,0 +1,28 @@ + +== Publisher Confirms + +NOTE: Publisher Confirms support is deprecated in favor of https://jakarta.ee/specifications/messaging/3.1/jakarta-messaging-spec-3.1.html#sending-messages-asynchronously-jms_spec-43[asynchronous sending] (JMS 2.0). + +link:https://rabbitmq.com/confirms.html#publisher-confirms[Publisher confirms] are a RabbitMQ extension to implement reliable +publishing. This feature builds on top of the AMQP protocol, but the JMS client +provides an API to use it. This allows to benefit from a reliability feature without +diverging too much from the JMS API. + +Publisher confirms are deactivated by default. They can be activated by setting +a `ConfirmListener` on the `RMQConnectionFactory`: + +[source,java,indent=0] +---- +RMQConnectionFactory connectionFactory = new RMQConnectionFactory(); +connectionFactory.setConfirmListener(context -> { + context.getMessage(); // the message that is confirmed/nack-ed + context.isAck(); // whether the message is confirmed or nack-ed +}); +---- + +Note the `ConfirmListener` is not a good place to execute long-running +tasks. Those should be executed in a dedicated thread, using e.g. an `ExecutorService`. + +Typical operations in a `ConfirmListener` are logging or message re-publishing (in case +of nacks). The link:https://rabbitmq.com/tutorials/tutorial-seven-java.html[publish confirms tutorial] provides more guidance. It aims for the +AMQP Java client, but principles remain the same for the JMS client. diff --git a/src/docs/asciidoc/rpc.adoc b/src/docs/asciidoc/rpc.adoc new file mode 100644 index 00000000..ce552a95 --- /dev/null +++ b/src/docs/asciidoc/rpc.adoc @@ -0,0 +1,171 @@ + +== Support for Request/Reply (a.k.a. RPC) + +It is possible to use JMS for synchronous request/reply use cases. +This pattern is commonly known as _Remote Procedure Call_ or _RPC_. + +=== With JMS API + +An RPC client can be implemented in pure JMS like the following: + +[source,java,indent=0] +---- +Message request = ... // create the request message +// set up reply-to queue and start listening on it +Destination replyQueue = session.createTemporaryQueue(); +message.setJMSReplyTo(replyQueue); +MessageConsumer responseConsumer = session.createConsumer(replyQueue); +BlockingQueue queue = new ArrayBlockingQueue<>(1); +responseConsumer.setMessageListener(msg -> queue.add(msg)); +// send request message +MessageProducer producer = session.createProducer("request.queue"); +producer.send(request); +// wait response for 5 seconds +Message response = queue.poll(5, TimeUnit.SECONDS); +// close the response consumer +responseConsumer.close(); +---- + +It's also possible to create a single reply-to destination instead of +a temporary destination for each request. This is more efficient but requires +to properly correlate the response with the request, by using e.g. +a correlation ID header. RabbitMQ's link:https://rabbitmq.com/direct-reply-to.html[direct reply-to] +is another alternative (see below). + +Note this sample uses a `MessageListener` and a `BlockingQueue` to wait +for the response. This implies a network roundtrip to register an AMQP +consumer and another one to close the consumer. +`MessageConsumer#receive` could have been used as well, in this case the JMS +client internally polls the reply destination to get the response, which can result in several +network roundtrips if the response takes some time to come. The request +call will also incur a constant penalty equals to the polling interval (100 milliseconds +by default). + +The server part looks like the following: + +[source,java,indent=0] +---- +// this is necessary when using temporary reply-to destinations +connectionFactory.setDeclareReplyToDestination(false); +... +MessageProducer replyProducer = session.createProducer(null); +MessageConsumer consumer = session.createConsumer("request.queue"); +consumer.setMessageListener(message -> { + try { + Destination replyQueue = message.getJMSReplyTo(); + if (replyQueue != null) { + // create response and send it + Message response = ... + replyProducer.send(replyQueue, response); + } + } catch (JMSException e) { + // deal with exception + } +}); +---- + +Note the `connectionFactory.setDeclareReplyToDestination(false)` +statement: it is necessary when using temporary reply-to destinations. +If this flag is not set to `false` on the RPC server side, the JMS +client will try to re-create the temporary reply-to destination, which will +interfere with the client-side declaration. + +See https://github.com/rabbitmq/rabbitmq-jms-client/blob/main/src/test/java/com/rabbitmq/integration/tests/RpcIT.java[this test] +for a full RPC example. + +The JMS client also supports link:https://rabbitmq.com/direct-reply-to.html[direct reply-to], which is faster as it doesn't imply +creating a temporary reply destination: + +[source,java,indent=0] +---- +Message request = ... +// use direct reply-to +RMQDestination replyQueue = new RMQDestination( + "amq.rabbitmq.reply-to", "", "amq.rabbitmq.reply-to", "amq.rabbitmq.reply-to" +); +replyQueue.setDeclared(true); // don't need to create this destination +message.setJMSReplyTo(replyQueue); +MessageConsumer responseConsumer = session.createConsumer(replyQueue); +BlockingQueue queue = new ArrayBlockingQueue<>(1); +responseConsumer.setMessageListener(msg -> queue.add(msg)); +// send request message +MessageProducer producer = session.createProducer("request.queue"); +producer.send(request); +// wait response for 5 seconds +Message response = queue.poll(5, TimeUnit.SECONDS); +// close the response consumer +responseConsumer.close(); +---- + +Using direct reply-to for JMS-based RPC has the following implications: + +* it uses automatically auto-acknowledgment +* the response must be a `BytesMessage` or a `TextMessage` as direct reply-to + is considered an <>. Use `response.setStringProperty("JMSType", "TextMessage")` + on the response message in the RPC server if you want to receive a `TextMessage` + on the client side. + +See https://github.com/rabbitmq/rabbitmq-jms-client/blob/main/src/test/java/com/rabbitmq/integration/tests/RpcWithAmqpDirectReplyIT.java[this test] for a full RPC example using direct reply-to. + +=== With Spring JMS + +https://docs.spring.io/spring-framework/docs/{spring-version}/reference/html/integration.html#jms[Spring JMS] +is a popular way to work with JMS as it avoids most of JMS boilerplate. + +The following sample shows how a client can perform RPC with the +`JmsTemplate`: + +[source,java,indent=0] +---- +// NB: do not create a new JmsTemplate for each request +JmsTemplate tpl = new JmsTemplate(connectionFactory); +tpl.setReceiveTimeout(5000); +Message response = tpl.sendAndReceive( + "request.queue", + session -> ... // create request message in MessageCreator +); +---- + +This is no different from any other JMS client. + +The `JmsTemplate` uses a temporary reply-to destination, +so the call to `connectionFactory.setDeclareReplyToDestination(false)` +on the RPC server side is necessary, just like with regular JMS. + +RPC with direct reply-to +must be implemented with a `SessionCallback`, as the reply destination +must be explicitly declared: + +[source,java,indent=0] +---- +// NB: do not create a new JmsTemplate for each request +JmsTemplate tpl = new JmsTemplate(connectionFactory); +Message response = tpl.execute(session -> { + Message request = ... // create request message + // setup direct reply-to as reply-to destination + RMQDestination replyQueue = new RMQDestination( + "amq.rabbitmq.reply-to", "", "amq.rabbitmq.reply-to", "amq.rabbitmq.reply-to" + ); + replyQueue.setDeclared(true); // no need to create this destination + message.setJMSReplyTo(replyQueue); + MessageConsumer responseConsumer = session.createConsumer(replyQueue); + BlockingQueue queue = new ArrayBlockingQueue<>(1); + responseConsumer.setMessageListener(msg -> queue.add(msg)); + // send request message + MessageProducer producer = session.createProducer(session.createQueue("request.queue")); + producer.send(message); + try { + // wait response for 5 seconds + Message response = queue.poll(5, TimeUnit.SECONDS); + // close the response consumer + responseConsumer.close(); + return response; + } catch (InterruptedException e) { + // deal with exception + } +}); +---- + +See https://github.com/rabbitmq/rabbitmq-jms-client/blob/main/src/test/spring/com/rabbitmq/integration/tests/RpcSpringJmsIT.java[this test] +for a full example of RPC with Spring JMS, including using a `@JmsListener` bean +for the server part.