All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rabbitmq.jms.client.RMQConnection Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
package com.rabbitmq.jms.client;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;

import jakarta.jms.*;
import jakarta.jms.IllegalStateException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.jms.util.WhiteListObjectInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ShutdownListener;
import com.rabbitmq.client.ShutdownSignalException;
import com.rabbitmq.jms.util.RMQJMSException;

/**
 * Implementation of the {@link Connection}, {@link QueueConnection} and {@link TopicConnection} interfaces.
 * A {@link RMQConnection} object holds a list of {@link RMQSession} objects as well as the actual
 * {link com.rabbitmq.client.Connection} object that represents the TCP connection to the RabbitMQ broker.
 * 

* This implementation also holds a reference to the executor service that is used by the connection so that we * can pause incoming messages. *

*/ public class RMQConnection implements Connection, QueueConnection, TopicConnection { public static final int NO_CHANNEL_QOS = -1; private final Logger logger = LoggerFactory.getLogger(RMQConnection.class); /** the TCP connection wrapper to the RabbitMQ broker */ private final com.rabbitmq.client.Connection rabbitConnection; /** Hard coded connection meta data returned in the call {@link #getMetaData()} call */ private static final ConnectionMetaData connectionMetaData = new RMQConnectionMetaData(); /** The client ID for this connection */ private String clientID; /** The exception listener */ private final AtomicReference exceptionListener = new AtomicReference(); /** The list of all {@link RMQSession} objects created by this connection */ private final List sessions = Collections. synchronizedList(new ArrayList()); /** value to see if this connection has been closed */ private volatile boolean closed = false; /** atomic flag to pause and unpause the connection consumers (see {@link #start()} and {@link #stop()} methods) */ private final AtomicBoolean stopped = new AtomicBoolean(true); /** maximum time (in ms) to wait for close() to complete */ private final long terminationTimeout; /** max number of messages to read from a browsed queue */ private final int queueBrowserReadMax; /** How long to wait for onMessage to return, in milliseconds */ private final int onMessageTimeoutMs; private static ConcurrentHashMap CLIENT_IDS = new ConcurrentHashMap<>(); /** List of all our topic subscriptions so we can track them on a per connection basis (maintained by sessions).*/ private final Subscriptions subscriptions = new Subscriptions(); /** This is used for JMSCTS test cases, as ClientID should only be configurable right after the connection has been created */ private volatile boolean canSetClientID = true; /** * QoS setting for channels created by this connection. * * @see com.rabbitmq.client.Channel#basicQos(int) */ private final int channelsQos; /** * Whether {@link MessageProducer} properties (delivery mode, * priority, TTL) take precedence over respective {@link Message} * properties or not. * Default is true. */ private final boolean preferProducerMessageProperty; /** * Whether requeue message on {@link RuntimeException} in the * {@link jakarta.jms.MessageListener} or not. * Default is false. * * @since 1.7.0 * @see RMQConnection#requeueOnTimeout */ private final boolean requeueOnMessageListenerException; /** * Whether to requeue a message that timed out or not. * * Only taken into account if requeueOnMessageListenerException is true. * Default is false. * * @since 2.3.0 * @see RMQConnection#requeueOnMessageListenerException */ private final boolean requeueOnTimeout; /** * Whether to commit nack on rollback or not. * Default is false. * * @since 1.14.0 */ private final boolean nackOnRollback; /** * Whether using auto-delete for server-named queues for non-durable topics. * If set to true, those queues will be deleted when the session is closed. * If set to false, queues will be deleted when the owning connection is closed. * Default is false. * @since 1.8.0 */ private final boolean cleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose; /** * Callback to customise properties of outbound AMQP messages. * * @since 1.9.0 */ private final BiFunction amqpPropertiesCustomiser; /** * Callback before sending a message. * * @since 1.11.0 */ private final SendingContextConsumer sendingContextConsumer; private final ReceivingContextConsumer receivingContextConsumer; /** * Classes in these packages can be transferred via ObjectMessage. * * @see WhiteListObjectInputStream */ private final List trustedPackages; private final boolean keepTextMessageType; private final DelayedMessageService delayedMessageService; /** * The reply-to strategy to use when handling reply to properties * on received messages. * * @Since 2.9.0 */ private final ReplyToStrategy replyToStrategy; /** * Creates an RMQConnection object. * @param connectionParams parameters for this connection */ public RMQConnection(ConnectionParams connectionParams) { if (connectionParams.willRequeueOnTimeout() && !connectionParams.willRequeueOnMessageListenerException()) { throw new IllegalArgumentException("requeueOnTimeout can be true only if requeueOnMessageListenerException is true as well"); } connectionParams.getRabbitConnection().addShutdownListener(new RMQConnectionShutdownListener()); this.rabbitConnection = connectionParams.getRabbitConnection(); this.terminationTimeout = connectionParams.getTerminationTimeout(); this.queueBrowserReadMax = connectionParams.getQueueBrowserReadMax(); this.onMessageTimeoutMs = connectionParams.getOnMessageTimeoutMs(); this.channelsQos = connectionParams.getChannelsQos(); this.preferProducerMessageProperty = connectionParams.willPreferProducerMessageProperty(); this.requeueOnMessageListenerException = connectionParams.willRequeueOnMessageListenerException(); this.nackOnRollback = connectionParams.willNackOnRollback(); this.cleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose = connectionParams.isCleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose(); this.amqpPropertiesCustomiser = connectionParams.getAmqpPropertiesCustomiser(); this.sendingContextConsumer = connectionParams.getSendingContextConsumer(); this.receivingContextConsumer = connectionParams.getReceivingContextConsumer(); this.trustedPackages = connectionParams.getTrustedPackages(); this.requeueOnTimeout = connectionParams.willRequeueOnTimeout(); this.keepTextMessageType = connectionParams.isKeepTextMessageType(); this.delayedMessageService = new DelayedMessageService(); this.replyToStrategy = connectionParams.getReplyToStrategy(); } /** * Creates an RMQConnection object. * @param rabbitConnection the TCP connection wrapper to the RabbitMQ broker * @param terminationTimeout timeout for close in milliseconds * @param queueBrowserReadMax maximum number of messages to read from a QueueBrowser (before filtering) * @param onMessageTimeoutMs how long to wait for onMessage to return, in milliseconds */ public RMQConnection(com.rabbitmq.client.Connection rabbitConnection, long terminationTimeout, int queueBrowserReadMax, int onMessageTimeoutMs) { this(new ConnectionParams() .setRabbitConnection(rabbitConnection) .setTerminationTimeout(terminationTimeout) .setQueueBrowserReadMax(queueBrowserReadMax) .setOnMessageTimeoutMs(onMessageTimeoutMs) ); } private static final long FIFTEEN_SECONDS_MS = 15000; private static final int TWO_SECONDS_MS = 2000; /** * Creates an RMQConnection object, with default termination timeout of 15 seconds, a 2 seconds timeout for onMessage, and unlimited reads from QueueBrowsers. * @param rabbitConnection the TCP connection wrapper to the RabbitMQ broker */ public RMQConnection(com.rabbitmq.client.Connection rabbitConnection) { this(rabbitConnection, FIFTEEN_SECONDS_MS, 0, TWO_SECONDS_MS); } /** For RMQSession to retrieve */ int getQueueBrowserReadMax() { return this.queueBrowserReadMax; } /** * {@inheritDoc} */ @Override public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException { logger.trace("transacted={}, acknowledgeMode={}", transacted, acknowledgeMode); illegalStateExceptionIfClosed(); freezeClientID(); RMQSession session = new RMQSession(new SessionParams() .setConnection(this) .setTransacted(transacted) .setOnMessageTimeoutMs(onMessageTimeoutMs) .setMode(acknowledgeMode) .setSubscriptions(this.subscriptions) .setPreferProducerMessageProperty(this.preferProducerMessageProperty) .setRequeueOnMessageListenerException(this.requeueOnMessageListenerException) .setNackOnRollback(this.nackOnRollback) .setCleanUpServerNamedQueuesForNonDurableTopics(this.cleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose) .setAmqpPropertiesCustomiser(this.amqpPropertiesCustomiser) .setSendingContextConsumer(this.sendingContextConsumer) .setReceivingContextConsumer(this.receivingContextConsumer) .setTrustedPackages(this.trustedPackages) .setRequeueOnTimeout(this.requeueOnTimeout) .setKeepTextMessageType(this.keepTextMessageType) .setDelayedMessageService(this.delayedMessageService) .setReplyToStrategy(this.replyToStrategy) ); this.sessions.add(session); return session; } private void freezeClientID() { this.canSetClientID = false; } private void illegalStateExceptionIfClosed() throws IllegalStateException { if (this.closed) throw new IllegalStateException("Connection is closed"); } /** * {@inheritDoc} */ @Override public String getClientID() throws JMSException { illegalStateExceptionIfClosed(); return this.clientID; } /** * {@inheritDoc} */ @Override public void setClientID(String clientID) throws JMSException { logger.trace("set ClientID to '{}'", clientID); illegalStateExceptionIfClosed(); if (!canSetClientID) throw new IllegalStateException("Client ID can only be set right after connection creation"); if (this.clientID==null) { if (CLIENT_IDS.putIfAbsent(clientID, clientID)==null) { this.clientID = clientID; } else { throw new InvalidClientIDException(String.format("A connection with client ID [%s] already exists.", clientID)); } } else { throw new IllegalStateException("Client ID already set."); } } public List getTrustedPackages() { return trustedPackages; } /** * {@inheritDoc} */ @Override public ConnectionMetaData getMetaData() throws JMSException { illegalStateExceptionIfClosed(); freezeClientID(); return connectionMetaData; } /** * {@inheritDoc} */ @Override public ExceptionListener getExceptionListener() throws JMSException { illegalStateExceptionIfClosed(); freezeClientID(); return this.exceptionListener.get(); } /** * {@inheritDoc} */ @Override public void setExceptionListener(ExceptionListener listener) throws JMSException { logger.trace("set ExceptionListener ({}) on connection ({})", listener, this); illegalStateExceptionIfClosed(); freezeClientID(); this.exceptionListener.set(listener); } /** * {@inheritDoc} */ @Override public void start() throws JMSException { logger.trace("starting connection ({})", this); illegalStateExceptionIfClosed(); freezeClientID(); if (stopped.compareAndSet(true, false)) { for (RMQSession session : this.sessions) { session.resume(); } } } /** * {@inheritDoc} */ @Override public void stop() throws JMSException { logger.trace("stopping connection ({})", this); illegalStateExceptionIfClosed(); freezeClientID(); if (stopped.compareAndSet(false, true)) { for (RMQSession session : this.sessions) { session.pause(); } } } /** * @return true if this connection is in a stopped state */ public boolean isStopped() { return stopped.get(); } /** * From the JMS Spec: *
*

This call blocks until a * receive or message listener in progress has completed. A blocked message consumer receive call returns null when * this message consumer is closed.

*
* {@inheritDoc} */ @Override public void close() throws JMSException { logger.trace("closing connection ({})", this); if (this.closed) return; this.closed = true; removeClientID(); // We null any exception listener since we don't want it driven during close(). this.exceptionListener.set(null); closeAllSessions(); this.delayedMessageService.close(); try { this.rabbitConnection.close(); } catch (ShutdownSignalException x) { //nothing to do } catch (IOException x) { if (!(x.getCause() instanceof ShutdownSignalException)) { throw new RMQJMSException(x); } } } private void removeClientID() throws JMSException { String cID = this.clientID; // even if closed! if (cID != null) CLIENT_IDS.remove(cID); } private void closeAllSessions() { for (RMQSession session : this.sessions) { try { session.internalClose(); } catch (Exception e) { if (e instanceof ShutdownSignalException) { // do nothing } else { logger.error("exception closing session ({})", session, e); } } } this.sessions.clear(); } Channel createRabbitChannel(boolean transactional) throws IOException { Channel channel = this.rabbitConnection.createChannel(); if(this.channelsQos != NO_CHANNEL_QOS) { channel.basicQos(channelsQos); } if (transactional) { channel.txSelect(); } return channel; } /** * {@inheritDoc} */ @Override public TopicSession createTopicSession(boolean transacted, int acknowledgeMode) throws JMSException { return (TopicSession) this.createSession(transacted, acknowledgeMode); } /** * @throws UnsupportedOperationException - optional method not implemented */ @Override public ConnectionConsumer createConnectionConsumer(Topic topic, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ @Override public QueueSession createQueueSession(boolean transacted, int acknowledgeMode) throws JMSException { return (QueueSession) this.createSession(transacted, acknowledgeMode); } /** * @throws UnsupportedOperationException - optional method not implemented */ @Override public ConnectionConsumer createConnectionConsumer(Queue queue, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException { throw new UnsupportedOperationException(); } /** * @throws UnsupportedOperationException - optional method not implemented */ @Override public ConnectionConsumer createConnectionConsumer(Destination destination, String messageSelector, ServerSessionPool sessionPool, int maxMessages) { throw new UnsupportedOperationException(); } /** * @throws UnsupportedOperationException - optional method not implemented */ @Override public ConnectionConsumer createDurableConnectionConsumer(Topic topic, String subscriptionName, String messageSelector, ServerSessionPool sessionPool, int maxMessages) { throw new UnsupportedOperationException(); } /* Internal methods. */ /** A connection must track all sessions that are created, * but when we call {@link RMQSession#close()} we must unregister this * session with the connection. * This method is called by {@link RMQSession#close()} and should not be called from anywhere else. * * @param session - the session that is being closed */ void sessionClose(RMQSession session) throws JMSException { logger.trace("internal:sessionClose({})", session); if (this.sessions.remove(session)) { session.internalClose(); } } boolean hasSessions() { return !this.sessions.isEmpty(); } long getTerminationTimeout() { return this.terminationTimeout; } @Override public String toString() { return new StringBuilder("RMQConnection{") .append("rabbitConnection=").append(this.rabbitConnection) .append(", stopped=").append(this.stopped.get()) .append(", queueBrowserReadMax=").append(this.queueBrowserReadMax) .append('}').toString(); } private class RMQConnectionShutdownListener implements ShutdownListener { @Override public void shutdownCompleted(ShutdownSignalException cause) { if ( null==exceptionListener.get() || cause.isInitiatedByApplication() ) return; // Ignore this exceptionListener.get().onException(new RMQJMSException(String.format("error in %s, connection closed, with reason %s", cause.getReference(), cause.getReason()), cause)); } } @Override public Session createSession(int sessionMode) throws JMSException { if (sessionMode == JMSContext.SESSION_TRANSACTED) { return this.createSession(true, sessionMode); } else { return this.createSession(false, sessionMode); } } @Override public Session createSession() throws JMSException { return this.createSession(false, Session.AUTO_ACKNOWLEDGE); } /** * @throws UnsupportedOperationException - optional method not implemented */ @Override public ConnectionConsumer createSharedConnectionConsumer(Topic topic, String subscriptionName, String messageSelector, ServerSessionPool sessionPool, int maxMessages) { throw new UnsupportedOperationException(); } /** * @throws UnsupportedOperationException - optional method not implemented */ @Override public ConnectionConsumer createSharedDurableConnectionConsumer(Topic topic, String subscriptionName, String messageSelector, ServerSessionPool sessionPool, int maxMessages) { throw new UnsupportedOperationException(); } /** * Gets the reply to strategy that should be followed if as reply to is * found on a received message. * * @return The reply to strategy. * * @since 2.9.0 */ public ReplyToStrategy getReplyToStrategy() { return replyToStrategy; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy