com.datastax.oss.pulsar.jms.PulsarConnection Maven / Gradle / Ivy
/*
* Copyright DataStax, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.datastax.oss.pulsar.jms;
import jakarta.jms.Connection;
import jakarta.jms.ConnectionConsumer;
import jakarta.jms.ConnectionMetaData;
import jakarta.jms.Destination;
import jakarta.jms.ExceptionListener;
import jakarta.jms.IllegalStateException;
import jakarta.jms.InvalidClientIDException;
import jakarta.jms.InvalidDestinationException;
import jakarta.jms.InvalidSelectorException;
import jakarta.jms.JMSException;
import jakarta.jms.MessageConsumer;
import jakarta.jms.Queue;
import jakarta.jms.QueueConnection;
import jakarta.jms.QueueSession;
import jakarta.jms.ServerSessionPool;
import jakarta.jms.Session;
import jakarta.jms.TemporaryQueue;
import jakarta.jms.TemporaryTopic;
import jakarta.jms.Topic;
import jakarta.jms.TopicConnection;
import jakarta.jms.TopicSession;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PulsarConnection implements Connection, QueueConnection, TopicConnection {
private final PulsarConnectionFactory factory;
private volatile ExceptionListener exceptionListener;
private final List sessions = new CopyOnWriteArrayList<>();
private final List temporaryDestinations =
new CopyOnWriteArrayList<>();
private final String connectionId;
private volatile boolean closed = false;
String clientId;
private volatile boolean allowSetClientId = true;
private final ReentrantReadWriteLock connectionPausedLock = new ReentrantReadWriteLock();
private final Condition pausedCondition = connectionPausedLock.writeLock().newCondition();;
private volatile boolean paused = true;
public PulsarConnection(PulsarConnectionFactory factory) throws JMSException {
this.factory = factory;
this.clientId = factory.getDefaultClientId();
if (this.clientId != null) {
factory.registerClientId(this.clientId);
connectionId = clientId + "_" + UUID.randomUUID().toString();
} else {
connectionId = UUID.randomUUID().toString();
}
}
public PulsarConnectionFactory getFactory() {
return factory;
}
/**
* Creates a {@code Session} object, specifying {@code transacted} and {@code acknowledgeMode}.
*
* This method has been superseded by the method {@code createSession(int sessionMode)} which
* specifies the same information using a single argument, and by the method {@code
* createSession()} which is for use in a Java EE JTA transaction. Applications should consider
* using those methods instead of this one.
*
*
The effect of setting the {@code transacted} and {@code acknowledgeMode} arguments depends
* on whether this method is called in a Java SE environment, in the Java EE application client
* container, or in the Java EE web or EJB container. If this method is called in the Java EE web
* or EJB container then the effect of setting the transacted} and {@code acknowledgeMode}
* arguments also depends on whether or not there is an active JTA transaction in progress.
*
*
In a Java SE environment or in the Java EE application client container:
*
*
* - If {@code transacted} is set to {@code true} then the session will use a local
* transaction which may subsequently be committed or rolled back by calling the session's
* {@code commit} or {@code rollback} methods. The argument {@code acknowledgeMode} is
* ignored.
*
- If {@code transacted} is set to {@code false} then the session will be non-transacted. In
* this case the argument {@code acknowledgeMode} is used to specify how messages received
* by this session will be acknowledged. The permitted values are {@code
* Session.CLIENT_ACKNOWLEDGE}, {@code Session.AUTO_ACKNOWLEDGE} and {@code
* Session.DUPS_OK_ACKNOWLEDGE}. For a definition of the meaning of these acknowledgement
* modes see the links below.
*
*
* In a Java EE web or EJB container, when there is an active JTA transaction in
* progress:
*
*
* - Both arguments {@code transacted} and {@code acknowledgeMode} are ignored. The session
* will participate in the JTA transaction and will be committed or rolled back when that
* transaction is committed or rolled back, not by calling the session's {@code commit} or
* {@code rollback} methods. Since both arguments are ignored, developers are recommended to
* use {@code createSession()}, which has no arguments, instead of this method.
*
*
* In the Java EE web or EJB container, when there is no active JTA transaction in
* progress:
*
*
* - If {@code transacted} is set to false and {@code acknowledgeMode} is set to {@code
* JMSContext.AUTO_ACKNOWLEDGE} or {@code Session.DUPS_OK_ACKNOWLEDGE} then the session will
* be non-transacted and messages will be acknowledged according to the value of {@code
* acknowledgeMode}.
*
- If {@code transacted} is set to false and {@code acknowledgeMode} is set to {@code
* JMSContext.CLIENT_ACKNOWLEDGE} then the JMS provider is recommended to ignore the
* specified parameters and instead provide a non-transacted, auto-acknowledged session.
* However the JMS provider may alternatively provide a non-transacted session with client
* acknowledgement.
*
- If {@code transacted} is set to true, then the JMS provider is recommended to ignore the
* specified parameters and instead provide a non-transacted, auto-acknowledged session.
* However the JMS provider may alternatively provide a local transacted session.
*
- Applications are recommended to set {@code transacted} to false and {@code
* acknowledgeMode} to {@code JMSContext.AUTO_ACKNOWLEDGE} or {@code
* Session.DUPS_OK_ACKNOWLEDGE} since since applications which set {@code transacted} to
* false and set {@code acknowledgeMode} to {@code JMSContext.CLIENT_ACKNOWLEDGE}, or which
* set {@code transacted} to true, may not be portable.
*
*
* Applications running in the Java EE web and EJB containers must not attempt to create more
* than one active (not closed) {@code Session} object per connection. If this method is called in
* a Java EE web or EJB container when an active {@code Session} object already exists for this
* connection then a {@code JMSException} may be thrown.
*
* @param transacted indicates whether the session will use a local transaction, except in the
* cases described above when this value is ignored..
* @param acknowledgeMode when transacted is false, indicates how messages received by the session
* will be acknowledged, except in the cases described above when this value is ignored.
* @return a newly created session
* @throws JMSException if the {@code Connection} object fails to create a session due to
*
* - some internal error,
*
- lack of support for the specific transaction and acknowledgement mode, or
*
- because this method is being called in a Java EE web or EJB application and an active
* session already exists for this connection.
*
*
* @see Session#AUTO_ACKNOWLEDGE
* @see Session#CLIENT_ACKNOWLEDGE
* @see Session#DUPS_OK_ACKNOWLEDGE
* @see Connection#createSession(int)
* @see Connection#createSession()
* @since JMS 1.1
*/
@Override
public PulsarSession createSession(boolean transacted, int acknowledgeMode) throws JMSException {
return createSession(transacted, acknowledgeMode, null);
}
PulsarSession createSession(
boolean transacted, int acknowledgeMode, ConsumerConfiguration overrideconsumerConfiguration)
throws JMSException {
checkNotClosed();
allowSetClientId = false;
PulsarSession session =
new PulsarSession(
transacted ? Session.SESSION_TRANSACTED : acknowledgeMode,
this,
overrideconsumerConfiguration);
sessions.add(session);
return session;
}
void checkNotClosed() throws JMSException {
if (closed) {
throw new IllegalStateException("This connection is closed");
}
}
/**
* Creates a {@code Session} object, specifying {@code sessionMode}.
*
* The effect of setting the {@code sessionMode} argument depends on whether this method is
* called in a Java SE environment, in the Java EE application client container, or in the Java EE
* web or EJB container. If this method is called in the Java EE web or EJB container then the
* effect of setting the {@code sessionMode} argument also depends on whether or not there is an
* active JTA transaction in progress.
*
*
In a Java SE environment or in the Java EE application client container:
*
*
* - If {@code sessionMode} is set to {@code Session.SESSION_TRANSACTED} then the session will
* use a local transaction which may subsequently be committed or rolled back by calling the
* session's {@code commit} or {@code rollback} methods.
*
- If {@code sessionMode} is set to any of {@code Session.CLIENT_ACKNOWLEDGE}, {@code
* Session.AUTO_ACKNOWLEDGE} or {@code Session.DUPS_OK_ACKNOWLEDGE}. then the session will
* be non-transacted and messages received by this session will be acknowledged according to
* the value of {@code sessionMode}. For a definition of the meaning of these
* acknowledgement modes see the links below.
*
*
* In a Java EE web or EJB container, when there is an active JTA transaction in
* progress:
*
*
* - The argument {@code sessionMode} is ignored. The session will participate in the JTA
* transaction and will be committed or rolled back when that transaction is committed or
* rolled back, not by calling the session's {@code commit} or {@code rollback} methods.
* Since the argument is ignored, developers are recommended to use {@code createSession()},
* which has no arguments, instead of this method.
*
*
* In the Java EE web or EJB container, when there is no active JTA transaction in
* progress:
*
*
* - If {@code sessionMode} is set to {@code Session.AUTO_ACKNOWLEDGE} or {@code
* Session.DUPS_OK_ACKNOWLEDGE} then the session will be non-transacted and messages will be
* acknowledged according to the value of {@code sessionMode}.
*
- If {@code sessionMode} is set to {@code Session.CLIENT_ACKNOWLEDGE} then the JMS provider
* is recommended to ignore the specified parameter and instead provide a non-transacted,
* auto-acknowledged session. However the JMS provider may alternatively provide a
* non-transacted session with client acknowledgement.
*
- If {@code sessionMode} is set to {@code Session.SESSION_TRANSACTED}, then the JMS
* provider is recommended to ignore the specified parameter and instead provide a
* non-transacted, auto-acknowledged session. However the JMS provider may alternatively
* provide a local transacted session.
*
- Applications are recommended to use only the values {@code Session.AUTO_ACKNOWLEDGE} and
* {@code Session.DUPS_OK_ACKNOWLEDGE} since applications which use {@code
* Session.CLIENT_ACKNOWLEDGE} or {@code Session.SESSION_TRANSACTED} may not be portable.
*
*
* Applications running in the Java EE web and EJB containers must not attempt to create more
* than one active (not closed) {@code Session} object per connection. If this method is called in
* a Java EE web or EJB container when an active {@code Session} object already exists for this
* connection then a {@code JMSException} may be thrown.
*
* @param sessionMode specifies the session mode that will be used, except in the cases described
* above when this value is ignored. Legal values are {@code JMSContext.SESSION_TRANSACTED},
* {@code JMSContext.CLIENT_ACKNOWLEDGE}, {@code JMSContext.AUTO_ACKNOWLEDGE} and {@code
* JMSContext.DUPS_OK_ACKNOWLEDGE}.
* @return a newly created session
* @throws JMSException if the {@code Connection} object fails to create a session due to
*
* - some internal error,
*
- lack of support for the specific transaction and acknowledgement mode, or
*
- because this method is being called in a Java EE web or EJB application and an active
* session already exists for this connection.
*
*
* @see Session#SESSION_TRANSACTED
* @see Session#AUTO_ACKNOWLEDGE
* @see Session#CLIENT_ACKNOWLEDGE
* @see Session#DUPS_OK_ACKNOWLEDGE
* @see Connection#createSession(boolean, int)
* @see Connection#createSession()
* @since JMS 2.0
*/
@Override
public PulsarSession createSession(int sessionMode) throws JMSException {
return createSession(sessionMode == Session.SESSION_TRANSACTED, sessionMode);
}
/**
* Creates a {@code Session} object, specifying no arguments.
*
* The behaviour of the session that is created depends on whether this method is called in a
* Java SE environment, in the Java EE application client container, or in the Java EE web or EJB
* container. If this method is called in the Java EE web or EJB container then the behaviour of
* the session also depends on whether or not there is an active JTA transaction in progress.
*
*
In a Java SE environment or in the Java EE application client container:
*
*
* - The session will be non-transacted and received messages will be acknowledged
* automatically using an acknowledgement mode of {@code Session.AUTO_ACKNOWLEDGE} For a
* definition of the meaning of this acknowledgement mode see the link below.
*
*
* In a Java EE web or EJB container, when there is an active JTA transaction in
* progress:
*
*
* - The session will participate in the JTA transaction and will be committed or rolled back
* when that transaction is committed or rolled back, not by calling the session's {@code
* commit} or {@code rollback} methods.
*
*
* In the Java EE web or EJB container, when there is no active JTA transaction in
* progress:
*
*
* - The session will be non-transacted and received messages will be acknowledged
* automatically using an acknowledgement mode of {@code Session.AUTO_ACKNOWLEDGE} For a
* definition of the meaning of this acknowledgement mode see the link below.
*
*
* Applications running in the Java EE web and EJB containers must not attempt to create more
* than one active (not closed) {@code Session} object per connection. If this method is called in
* a Java EE web or EJB container when an active {@code Session} object already exists for this
* connection then a {@code JMSException} may be thrown.
*
* @return a newly created session
* @throws JMSException if the {@code Connection} object fails to create a session due to
*
* - some internal error or
*
- because this method is being called in a Java EE web or EJB application and an active
* session already exists for this connection.
*
*
* @see Session#AUTO_ACKNOWLEDGE
* @see Connection#createSession(boolean, int)
* @see Connection#createSession(int)
* @since JMS 2.0
*/
@Override
public PulsarSession createSession() throws JMSException {
allowSetClientId = false;
return createSession(Session.AUTO_ACKNOWLEDGE);
}
/**
* Gets the client identifier for this connection.
*
* This value is specific to the JMS provider. It is either preconfigured by an administrator
* in a {@code ConnectionFactory} object or assigned dynamically by the application by calling the
* {@code setClientID} method.
*
* @return the unique client identifier
* @throws JMSException if the JMS provider fails to return the client ID for this connection due
* to some internal error.
*/
@Override
public String getClientID() throws JMSException {
checkNotClosed();
return clientId;
}
/**
* Sets the client identifier for this connection.
*
*
The preferred way to assign a JMS client's client identifier is for it to be configured in a
* client-specific {@code ConnectionFactory} object and transparently assigned to the {@code
* Connection} object it creates.
*
*
Alternatively, a client can set a connection's client identifier using a provider-specific
* value. The facility to set a connection's client identifier explicitly is not a mechanism for
* overriding the identifier that has been administratively configured. It is provided for the
* case where no administratively specified identifier exists. If one does exist, an attempt to
* change it by setting it must throw an {@code IllegalStateException}. If a client sets the
* client identifier explicitly, it must do so immediately after it creates the connection and
* before any other action on the connection is taken. After this point, setting the client
* identifier is a programming error that should throw an {@code IllegalStateException}.
*
*
The purpose of the client identifier is to associate a connection and its objects with a
* state maintained on behalf of the client by a provider. The only such state identified by the
* JMS API is that required to support durable subscriptions.
*
*
If another connection with the same {@code clientID} is already running when this method is
* called, the JMS provider should detect the duplicate ID and throw an {@code
* InvalidClientIDException}.
*
*
This method must not be used in a Java EE web or EJB application. Doing so may cause a
* {@code JMSException} to be thrown though this is not guaranteed.
*
* @param clientID the unique client identifier
* @throws JMSException if the JMS provider fails to set the client ID for the the connection for
* one of the following reasons:
*
* - an internal error has occurred or
*
- this method has been called in a Java EE web or EJB application (though it is not
* guaranteed that an exception is thrown in this case)
*
*
* @throws InvalidClientIDException if the JMS client specifies an invalid or duplicate client ID.
* @throws IllegalStateException if the JMS client attempts to set a connection's client ID at the
* wrong time or when it has been administratively configured.
*/
@Override
public void setClientID(String clientID) throws JMSException {
log.info("setClientID {} on {}, factory {}", clientID, this, factory);
checkNotClosed();
if (!allowSetClientId) {
throw new IllegalStateException("Cannot set clientId after performing any other operation");
}
if (factory.getDefaultClientId() != null) {
throw new IllegalStateException(
"ClientId has be administratively configured as "
+ factory.getDefaultClientId()
+ ", you cannot change it");
}
if (clientID == null || clientID.isEmpty()) {
throw new InvalidClientIDException("Invalid empty clientId");
}
if (this.clientId != null) {
throw new InvalidClientIDException("cannot set again the clientId");
}
allowSetClientId = false;
factory.registerClientId(clientID);
this.clientId = clientID;
}
/**
* Gets the metadata for this connection.
*
* @return the connection metadata
* @throws JMSException if the JMS provider fails to get the connection metadata for this
* connection.
* @see ConnectionMetaData
*/
@Override
public ConnectionMetaData getMetaData() throws JMSException {
checkNotClosed();
return PulsarConnectionMetadata.INSTANCE;
}
/**
* Gets the {@code ExceptionListener} object for this connection. Not every {@code Connection} has
* an {@code ExceptionListener} associated with it.
*
* @return the {@code ExceptionListener} for this connection, or null. if no {@code
* ExceptionListener} is associated with this connection.
* @throws JMSException if the JMS provider fails to get the {@code ExceptionListener} for this
* connection.
* @see Connection#setExceptionListener
*/
@Override
public ExceptionListener getExceptionListener() throws JMSException {
checkNotClosed();
return exceptionListener;
}
/**
* Sets an exception listener for this connection.
*
* If a JMS provider detects a serious problem with a connection, it informs the connection's
* {@code ExceptionListener}, if one has been registered. It does this by calling the listener's
* {@code onException} method, passing it a {@code JMSException} object describing the problem.
*
*
An exception listener allows a client to be notified of a problem asynchronously. Some
* connections only consume messages, so they would have no other way to learn their connection
* has failed.
*
*
A connection serializes execution of its {@code ExceptionListener}.
*
*
A JMS provider should attempt to resolve connection problems itself before it notifies the
* client of them.
*
*
This method must not be used in a Java EE web or EJB application. Doing so may cause a
* {@code JMSException} to be thrown though this is not guaranteed.
*
* @param listener the exception listener
* @throws JMSException if the JMS provider fails to set the exception listener for one of the
* following reasons:
*
* - an internal error has occurred or
*
- this method has been called in a Java EE web or EJB application (though it is not
* guaranteed that an exception is thrown in this case)
*
*/
@Override
public void setExceptionListener(ExceptionListener listener) throws JMSException {
checkNotClosed();
this.exceptionListener = listener;
}
/**
* Starts (or restarts) a connection's delivery of incoming messages. A call to {@code start} on a
* connection that has already been started is ignored.
*
* @throws JMSException if the JMS provider fails to start message delivery due to some internal
* error.
* @see Connection#stop
*/
@Override
public void start() throws JMSException {
checkNotInSessionMessageListener();
checkNotClosed();
connectionPausedLock.writeLock().lock();
try {
paused = false;
pausedCondition.signalAll();
} catch (Throwable err) {
throw Utils.handleException(err);
} finally {
connectionPausedLock.writeLock().unlock();
}
}
/**
* Temporarily stops a connection's delivery of incoming messages. Delivery can be restarted using
* the connection's {@code start} method. When the connection is stopped, delivery to all the
* connection's message consumers is inhibited: synchronous receives block, and messages are not
* delivered to message listeners.
*
* Stopping a connection has no effect on its ability to send messages. A call to {@code stop}
* on a connection that has already been stopped is ignored.
*
*
A call to {@code stop} must not return until delivery of messages has paused. This means
* that a client can rely on the fact that none of its message listeners will be called and that
* all threads of control waiting for {@code receive} calls to return will not return with a
* message until the connection is restarted. The receive timers for a stopped connection continue
* to advance, so receives may time out while the connection is stopped.
*
*
If message listeners are running when {@code stop} is invoked, the {@code stop} call must
* wait until all of them have returned before it may return. While these message listeners are
* completing, they must have the full services of the connection available to them.
*
*
However if the {@code stop} method is called from a message listener on its own connection,
* then it will either fail and throw a {@code jakarta.jms.IllegalStateException}, or it will
* succeed and stop the connection, blocking until all other message listeners that may have been
* running have returned.
*
*
Since two alternative behaviors are permitted in this case, applications should avoid
* calling {@code stop} from a message listener on its own Connection because this is not
* portable.
*
*
For the avoidance of doubt, if an exception listener for this connection is running when
* {@code stop} is invoked, there is no requirement for the {@code stop} call to wait until the
* exception listener has returned before it may return.
*
*
This method must not be used in a Java EE web or EJB application. Doing so may cause a
* {@code JMSException} to be thrown though this is not guaranteed.
*
* @throws IllegalStateException this method has been called by a MessageListener
on
* its own Connection
* @throws JMSException if the JMS provider fails to stop message delivery for one of the
* following reasons:
*
* - an internal error has occurred or
*
- this method has been called in a Java EE web or EJB application (though it is not
* guaranteed that an exception is thrown in this case)
*
*
* @see Connection#start
*/
@Override
public void stop() throws JMSException {
checkNotInSessionMessageListener();
checkNotClosed();
connectionPausedLock.writeLock().lock();
try {
paused = true;
pausedCondition.signalAll();
} catch (Throwable err) {
throw Utils.handleException(err);
} finally {
connectionPausedLock.writeLock().unlock();
}
}
private void checkNotInSessionMessageListener() throws JMSException {
for (PulsarSession session : sessions) {
Utils.checkNotOnMessageListener(session);
}
}
/**
* Closes the connection.
*
* Since a provider typically allocates significant resources outside the JVM on behalf of a
* connection, clients should close these resources when they are not needed. Relying on garbage
* collection to eventually reclaim these resources may not be timely enough.
*
*
There is no need to close the sessions, producers, and consumers of a closed connection.
*
*
Closing a connection causes all temporary destinations to be deleted.
*
*
When this method is invoked, it should not return until message processing has been shut
* down in an orderly fashion. This means that all message listeners that may have been running
* have returned, and that all pending receives have returned.
*
*
However if the close method is called from a message listener on its own connection, then it
* will either fail and throw a {@code jakarta.jms.IllegalStateException}, or it will succeed and
* close the connection, blocking until all other message listeners that may have been running
* have returned, and all pending receive calls have completed. If close succeeds and the
* acknowledge mode of the session is set to {@code AUTO_ACKNOWLEDGE}, the current message will
* still be acknowledged automatically when the {@code onMessage} call completes. Since two
* alternative behaviors are permitted in this case, applications should avoid calling close from
* a message listener on its own connection because this is not portable.
*
*
A close terminates all pending message receives on the connection's sessions' consumers. The
* receives may return with a message or with null, depending on whether there was a message
* available at the time of the close. If one or more of the connection's sessions' message
* listeners is processing a message at the time when connection {@code close} is invoked, all the
* facilities of the connection and its sessions must remain available to those listeners until
* they return control to the JMS provider.
*
*
This method must not return until any incomplete asynchronous send operations for this
* Connection
have been completed and any CompletionListener
callbacks
* have returned. Incomplete sends should be allowed to complete normally unless an error occurs.
*
*
For the avoidance of doubt, if an exception listener for this connection is running when
* {@code close} is invoked, there is no requirement for the {@code close} call to wait until the
* exception listener has returned before it may return.
*
*
Closing a connection causes any of its sessions' transactions in progress to be rolled back.
* In the case where a session's work is coordinated by an external transaction manager, a
* session's {@code commit} and {@code rollback} methods are not used and the result of a closed
* session's work is determined later by the transaction manager. Closing a connection does NOT
* force an acknowledgment of client-acknowledged sessions.
*
*
A CompletionListener
callback method must not call close
on its
* own Connection
. Doing so will cause an IllegalStateException
to be
* thrown.
*
*
Invoking the {@code acknowledge} method of a received message from a closed connection's
* session must throw an {@code IllegalStateException}. Closing a closed connection must NOT throw
* an exception.
*
* @throws IllegalStateException
*
* - this method has been called by a
MessageListener
on its own
* Connection
* - this method has been called by a
CompletionListener
callback method on
* its own Connection
*
*
* @throws JMSException if the JMS provider fails to close the connection due to some internal
* error. For example, a failure to release resources or to close a socket connection can
* cause this exception to be thrown.
*/
@Override
public void close() throws JMSException {
checkNotInSessionMessageListener();
if (closed) {
return;
}
closed = true;
for (PulsarSession session : sessions) {
session.close();
}
sessions.clear();
for (PulsarTemporaryDestination temporaryDestination : new ArrayList<>(temporaryDestinations)) {
try {
temporaryDestination.delete();
} catch (JMSException err) {
log.error("Cannot delete temporary destination {}", temporaryDestination.topicName, err);
}
}
temporaryDestinations.clear();
factory.unregisterConnection(this);
}
/**
* Creates a connection consumer for this connection (optional operation) on the specific
* destination.
*
* This is an expert facility not used by ordinary JMS clients.
*
*
This method must not be used in a Java EE web or EJB application. Doing so may cause a
* {@code JMSException} to be thrown though this is not guaranteed.
*
* @param destination the destination to access
* @param messageSelector only messages with properties matching the message selector expression
* are delivered. A value of null or an empty string indicates that there is no message
* selector for the message consumer.
* @param sessionPool the server session pool to associate with this connection consumer
* @param maxMessages the maximum number of messages that can be assigned to a server session at
* one time
* @return the connection consumer
* @throws InvalidDestinationException if an invalid destination is specified.
* @throws InvalidSelectorException if the message selector is invalid.
* @throws JMSException if the {@code Connection} object fails to create a connection consumer for
* one of the following reasons:
*
* - an internal error has occurred
*
- invalid arguments for {@code sessionPool} and {@code messageSelector} or
*
- this method has been called in a Java EE web or EJB application (though it is not
* guaranteed that an exception is thrown in this case)
*
*
* @see ConnectionConsumer
* @since JMS 1.1
*/
@Override
public ConnectionConsumer createConnectionConsumer(
Destination destination,
String messageSelector,
ServerSessionPool sessionPool,
int maxMessages)
throws JMSException {
checkNotClosed();
return buildConnectionConsumer(
sessionPool,
maxMessages,
(session) -> session.createConsumer(destination, messageSelector));
}
/**
* Creates a connection consumer for this connection (optional operation) on the specific topic
* using a shared non-durable subscription with the specified name.
*
* This is an expert facility not used by ordinary JMS clients.
*
*
This method must not be used in a Java EE web or EJB application. Doing so may cause a
* {@code JMSException} to be thrown though this is not guaranteed.
*
* @param topic the topic to access
* @param subscriptionName the name used to identify the shared non-durable subscription
* @param messageSelector only messages with properties matching the message selector expression
* are delivered. A value of null or an empty string indicates that there is no message
* selector for the message consumer.
* @param sessionPool the server session pool to associate with this connection consumer
* @param maxMessages the maximum number of messages that can be assigned to a server session at
* one time
* @return the connection consumer
* @throws IllegalStateException if called on a {@code QueueConnection}
* @throws InvalidDestinationException if an invalid destination is specified.
* @throws InvalidSelectorException if the message selector is invalid.
* @throws JMSException if the {@code Connection} object fails to create a connection consumer for
* one of the following reasons:
*
* - an internal error has occurred
*
- invalid arguments for {@code sessionPool} and {@code messageSelector} or
*
- this method has been called in a Java EE web or EJB application (though it is not
* guaranteed that an exception is thrown in this case)
*
*
* @see ConnectionConsumer
* @since JMS 2.0
*/
@Override
public ConnectionConsumer createSharedConnectionConsumer(
Topic topic,
String subscriptionName,
String messageSelector,
ServerSessionPool sessionPool,
int maxMessages)
throws JMSException {
checkNotClosed();
return buildConnectionConsumer(
sessionPool,
maxMessages,
session -> session.createSharedConsumer(topic, subscriptionName, messageSelector));
}
/**
* Creates a connection consumer for this connection (optional operation) on the specific topic
* using an unshared durable subscription with the specified name.
*
* This is an expert facility not used by ordinary JMS clients.
*
*
This method must not be used in a Java EE web or EJB application. Doing so may cause a
* {@code JMSException} to be thrown though this is not guaranteed.
*
* @param topic topic to access
* @param subscriptionName the name used to identify the unshared durable subscription
* @param messageSelector only messages with properties matching the message selector expression
* are delivered. A value of null or an empty string indicates that there is no message
* selector for the message consumer.
* @param sessionPool the server session pool to associate with this durable connection consumer
* @param maxMessages the maximum number of messages that can be assigned to a server session at
* one time
* @return the durable connection consumer
* @throws IllegalStateException if called on a {@code QueueConnection}
* @throws InvalidDestinationException if an invalid destination is specified.
* @throws InvalidSelectorException if the message selector is invalid.
* @throws JMSException if the {@code Connection} object fails to create a connection consumer for
* one of the following reasons:
*
* - an internal error has occurred
*
- invalid arguments for {@code sessionPool} and {@code messageSelector} or
*
- this method has been called in a Java EE web or EJB application (though it is not
* guaranteed that an exception is thrown in this case)
*
*
* @see ConnectionConsumer
* @since JMS 1.1
*/
@Override
public ConnectionConsumer createDurableConnectionConsumer(
Topic topic,
String subscriptionName,
String messageSelector,
ServerSessionPool sessionPool,
int maxMessages)
throws JMSException {
checkNotClosed();
return buildConnectionConsumer(
sessionPool,
maxMessages,
session -> session.createDurableConsumer(topic, subscriptionName, messageSelector, false));
}
/**
* Creates a connection consumer for this connection (optional operation) on the specific topic
* using a shared durable subscription with the specified name.
*
* This is an expert facility not used by ordinary JMS clients.
*
*
This method must not be used in a Java EE web or EJB application. Doing so may cause a
* {@code JMSException} to be thrown though this is not guaranteed.
*
* @param topic topic to access
* @param subscriptionName the name used to identify the shared durable subscription
* @param messageSelector only messages with properties matching the message selector expression
* are delivered. A value of null or an empty string indicates that there is no message
* selector for the message consumer.
* @param sessionPool the server session pool to associate with this durable connection consumer
* @param maxMessages the maximum number of messages that can be assigned to a server session at
* one time
* @return the durable connection consumer
* @throws IllegalStateException if called on a {@code QueueConnection}
* @throws InvalidDestinationException if an invalid destination is specified.
* @throws InvalidSelectorException if the message selector is invalid.
* @throws JMSException if the {@code Connection} object fails to create a connection consumer for
* one of the following reasons:
*
* - an internal error has occurred
*
- invalid arguments for {@code sessionPool} and {@code messageSelector} or
*
- this method has been called in a Java EE web or EJB application (though it is not
* guaranteed that an exception is thrown in this case)
*
*
* @see ConnectionConsumer
* @since JMS 2.0
*/
@Override
public ConnectionConsumer createSharedDurableConnectionConsumer(
Topic topic,
String subscriptionName,
String messageSelector,
ServerSessionPool sessionPool,
int maxMessages)
throws JMSException {
checkNotClosed();
return buildConnectionConsumer(
sessionPool,
maxMessages,
session -> session.createSharedDurableConsumer(topic, subscriptionName, messageSelector));
}
public void unregisterSession(PulsarSession session) {
sessions.remove(session);
}
public String prependClientId(String subscriptionName, boolean allowUnset) throws JMSException {
if (clientId != null) {
return clientId + "_" + subscriptionName;
} else {
if (allowUnset) {
return subscriptionName;
} else {
throw new IllegalStateException("ClientID must be set");
}
}
}
public void setAllowSetClientId(boolean value) {
allowSetClientId = value;
}
public T executeInConnectionPausedLock(Utils.SupplierWithException run, int timeoutMillis)
throws JMSException {
boolean executedInTime = true;
connectionPausedLock.readLock().lock();
try {
if (paused) {
connectionPausedLock.readLock().unlock();
connectionPausedLock.writeLock().lock();
try {
while (paused) {
if (timeoutMillis > 0) {
executedInTime = pausedCondition.await(timeoutMillis, TimeUnit.MILLISECONDS);
if (!executedInTime) {
break;
}
} else {
pausedCondition.await();
}
}
connectionPausedLock.readLock().lock();
} finally {
connectionPausedLock.writeLock().unlock();
}
}
if (!executedInTime) {
return null;
}
return run.run();
} catch (Throwable err) {
throw Utils.handleException(err);
} finally {
connectionPausedLock.readLock().unlock(); // let writers in
}
}
public boolean isStarted() {
connectionPausedLock.readLock().lock();
try {
return !paused;
} finally {
connectionPausedLock.readLock().unlock();
}
}
public TemporaryQueue createTemporaryQueue(PulsarSession session) throws JMSException {
checkNotClosed();
String name =
"persistent://" + factory.getSystemNamespace() + "/jms-temp-queue-" + UUID.randomUUID();
createPulsarTemporaryTopic(name);
PulsarTemporaryQueue res = new PulsarTemporaryQueue(name, session);
temporaryDestinations.add(res);
return res;
}
public TemporaryTopic createTemporaryTopic(PulsarSession session) throws JMSException {
checkNotClosed();
String name =
"persistent://" + factory.getSystemNamespace() + "/jms-temp-topic-" + UUID.randomUUID();
createPulsarTemporaryTopic(name);
PulsarTemporaryTopic res = new PulsarTemporaryTopic(name, session);
temporaryDestinations.add(res);
return res;
}
@Override
public QueueSession createQueueSession(boolean b, int i) throws JMSException {
return createSession(b, i).emulateLegacySession(true, false);
}
@Override
public ConnectionConsumer createConnectionConsumer(
Queue queue, String messageSelector, ServerSessionPool serverSessionPool, int maxMessages)
throws JMSException {
checkNotClosed();
return buildConnectionConsumer(
serverSessionPool,
maxMessages,
(session) -> session.createConsumer(queue, messageSelector));
}
@Override
public TopicSession createTopicSession(boolean b, int i) throws JMSException {
return createSession(b, i).emulateLegacySession(false, true);
}
@Override
public ConnectionConsumer createConnectionConsumer(
Topic topic, String messageSelector, ServerSessionPool serverSessionPool, int maxMessages)
throws JMSException {
return buildConnectionConsumer(
serverSessionPool,
maxMessages,
(session) -> session.createConsumer(topic, messageSelector));
}
void removeTemporaryDestination(PulsarTemporaryDestination pulsarTemporaryDestination) {
temporaryDestinations.remove(pulsarTemporaryDestination);
}
public String getConnectionId() {
return connectionId;
}
@FunctionalInterface
private interface ConsumerBuilder {
MessageConsumer build(Session session) throws JMSException;
}
private ConnectionConsumer buildConnectionConsumer(
ServerSessionPool sessionPool, int maxMessages, ConsumerBuilder consumerBuilder)
throws JMSException {
// create one session
PulsarSession dispatcherSession =
createSession(false, PulsarJMSConstants.INDIVIDUAL_ACKNOWLEDGE);
PulsarMessageConsumer consumer =
(PulsarMessageConsumer) consumerBuilder.build(dispatcherSession);
PulsarConnectionConsumer connectionConsumer =
new PulsarConnectionConsumer(dispatcherSession, consumer, sessionPool, maxMessages);
connectionConsumer.start();
return connectionConsumer;
}
private void createPulsarTemporaryTopic(String name) throws JMSException {
try {
factory.getPulsarAdmin().topics().createNonPartitionedTopic(name);
} catch (IllegalStateException err) {
if (!factory.isAllowTemporaryTopicWithoutAdmin()) {
throw Utils.handleException(err);
}
log.warn(
"Skipping creation of nonPartitionedTopic {} as jms.allowTemporaryTopicWithoutAdmin=true",
name,
err);
} catch (Exception err) {
throw Utils.handleException(err);
}
}
void refreshServerSideSelectors() {
sessions.forEach(
s -> {
s.refreshServerSideSelectors();
});
}
}