
org.apache.activemq.artemis.jms.client.ActiveMQConnection Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.activemq.artemis.jms.client;
import javax.jms.ConnectionConsumer;
import javax.jms.ConnectionMetaData;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.IllegalStateException;
import javax.jms.InvalidClientIDException;
import javax.jms.JMSException;
import javax.jms.JMSRuntimeException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueSession;
import javax.jms.ServerSessionPool;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicSession;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Set;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.client.ClientSession;
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.api.core.client.FailoverEventListener;
import org.apache.activemq.artemis.api.core.client.FailoverEventType;
import org.apache.activemq.artemis.api.core.client.SessionFailureListener;
import org.apache.activemq.artemis.api.jms.ActiveMQJMSConstants;
import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
import org.apache.activemq.artemis.core.version.Version;
import org.apache.activemq.artemis.reader.MessageUtil;
import org.apache.activemq.artemis.utils.ConcurrentHashSet;
import org.apache.activemq.artemis.utils.UUIDGenerator;
import org.apache.activemq.artemis.utils.VersionLoader;
/**
* ActiveMQ Artemis implementation of a JMS Connection.
*
* The flat implementation of {@link TopicConnection} and {@link QueueConnection} is per design,
* following the common usage of these as one flat API in JMS 1.1.
*/
public class ActiveMQConnection extends ActiveMQConnectionForContextImpl implements TopicConnection, QueueConnection {
// Constants ------------------------------------------------------------------------------------
public static final int TYPE_GENERIC_CONNECTION = 0;
public static final int TYPE_QUEUE_CONNECTION = 1;
public static final int TYPE_TOPIC_CONNECTION = 2;
public static final String EXCEPTION_FAILOVER = "FAILOVER";
public static final String EXCEPTION_DISCONNECT = "DISCONNECT";
public static final SimpleString CONNECTION_ID_PROPERTY_NAME = MessageUtil.CONNECTION_ID_PROPERTY_NAME;
// Static ---------------------------------------------------------------------------------------
// Attributes -----------------------------------------------------------------------------------
private final int connectionType;
private final Set sessions = new ConcurrentHashSet();
private final Set tempQueues = new ConcurrentHashSet();
private final Set knownDestinations = new ConcurrentHashSet();
private volatile boolean hasNoLocal;
private volatile ExceptionListener exceptionListener;
private volatile FailoverEventListener failoverEventListener;
private volatile boolean justCreated = true;
private volatile ConnectionMetaData metaData;
private volatile boolean closed;
private volatile boolean started;
private String clientID;
private final ClientSessionFactory sessionFactory;
private final SimpleString uid;
private final String username;
private final String password;
private final SessionFailureListener listener = new JMSFailureListener(this);
private final FailoverEventListener failoverListener = new FailoverEventListenerImpl(this);
private final Version thisVersion;
private final int dupsOKBatchSize;
private final int transactionBatchSize;
private ClientSession initialSession;
private final Exception creationStack;
private ActiveMQConnectionFactory factoryReference;
// Constructors ---------------------------------------------------------------------------------
public ActiveMQConnection(final String username,
final String password,
final int connectionType,
final String clientID,
final int dupsOKBatchSize,
final int transactionBatchSize,
final ClientSessionFactory sessionFactory) {
this.username = username;
this.password = password;
this.connectionType = connectionType;
this.clientID = clientID;
this.sessionFactory = sessionFactory;
uid = UUIDGenerator.getInstance().generateSimpleStringUUID();
thisVersion = VersionLoader.getVersion();
this.dupsOKBatchSize = dupsOKBatchSize;
this.transactionBatchSize = transactionBatchSize;
creationStack = new Exception();
}
/**
* This internal method serves basically the Resource Adapter.
* The resource adapter plays with an XASession and a non XASession.
* When there is no enlisted transaction, the EE specification mandates that the commit should
* be done as if it was a nonXA Session (i.e. SessionTransacted).
* For that reason we have this method to force that nonXASession, since the JMS Javadoc
* mandates createSession to return a XASession.
*/
public Session createNonXASession(final boolean transacted, final int acknowledgeMode) throws JMSException {
checkClosed();
return createSessionInternal(false, transacted, acknowledgeMode, ActiveMQConnection.TYPE_GENERIC_CONNECTION);
}
/**
* This internal method serves basically the Resource Adapter.
* The resource adapter plays with an XASession and a non XASession.
* When there is no enlisted transaction, the EE specification mandates that the commit should
* be done as if it was a nonXA Session (i.e. SessionTransacted).
* For that reason we have this method to force that nonXASession, since the JMS Javadoc
* mandates createSession to return a XASession.
*/
public Session createNonXATopicSession(final boolean transacted, final int acknowledgeMode) throws JMSException {
checkClosed();
return createSessionInternal(false, transacted, acknowledgeMode, ActiveMQConnection.TYPE_TOPIC_CONNECTION);
}
/**
* This internal method serves basically the Resource Adapter.
* The resource adapter plays with an XASession and a non XASession.
* When there is no enlisted transaction, the EE specification mandates that the commit should
* be done as if it was a nonXA Session (i.e. SessionTransacted).
* For that reason we have this method to force that nonXASession, since the JMS Javadoc
* mandates createSession to return a XASession.
*/
public Session createNonXAQueueSession(final boolean transacted, final int acknowledgeMode) throws JMSException {
checkClosed();
return createSessionInternal(false, transacted, acknowledgeMode, ActiveMQConnection.TYPE_QUEUE_CONNECTION);
}
// Connection implementation --------------------------------------------------------------------
public synchronized Session createSession(final boolean transacted, final int acknowledgeMode) throws JMSException {
checkClosed();
return createSessionInternal(false, transacted, checkAck(transacted, acknowledgeMode), ActiveMQConnection.TYPE_GENERIC_CONNECTION);
}
public String getClientID() throws JMSException {
checkClosed();
return clientID;
}
public void setClientID(final String clientID) throws JMSException {
checkClosed();
if (this.clientID != null) {
throw new IllegalStateException("Client id has already been set");
}
if (!justCreated) {
throw new IllegalStateException("setClientID can only be called directly after the connection is created");
}
try {
initialSession.addUniqueMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY, clientID);
}
catch (ActiveMQException e) {
if (e.getType() == ActiveMQExceptionType.DUPLICATE_METADATA) {
throw new InvalidClientIDException("clientID=" + clientID + " was already set into another connection");
}
}
this.clientID = clientID;
try {
this.addSessionMetaData(initialSession);
}
catch (ActiveMQException e) {
JMSException ex = new JMSException("Internal error setting metadata jms-client-id");
ex.setLinkedException(e);
ex.initCause(e);
throw ex;
}
justCreated = false;
}
public ConnectionMetaData getMetaData() throws JMSException {
checkClosed();
justCreated = false;
if (metaData == null) {
metaData = new ActiveMQConnectionMetaData(thisVersion);
}
return metaData;
}
public ExceptionListener getExceptionListener() throws JMSException {
checkClosed();
justCreated = false;
return exceptionListener;
}
public void setExceptionListener(final ExceptionListener listener) throws JMSException {
checkClosed();
exceptionListener = listener;
justCreated = false;
}
public synchronized void start() throws JMSException {
checkClosed();
for (ActiveMQSession session : sessions) {
session.start();
}
justCreated = false;
started = true;
}
public synchronized void signalStopToAllSessions() {
for (ActiveMQSession session : sessions) {
ClientSession coreSession = session.getCoreSession();
if (coreSession instanceof ClientSessionInternal) {
ClientSessionInternal internalSession = (ClientSessionInternal) coreSession;
internalSession.setStopSignal();
}
}
}
public synchronized void stop() throws JMSException {
threadAwareContext.assertNotMessageListenerThread();
checkClosed();
for (ActiveMQSession session : sessions) {
session.stop();
}
justCreated = false;
started = false;
}
public final synchronized void close() throws JMSException {
threadAwareContext.assertNotCompletionListenerThread();
threadAwareContext.assertNotMessageListenerThread();
if (closed) {
return;
}
sessionFactory.close();
try {
for (ActiveMQSession session : new HashSet(sessions)) {
session.close();
}
try {
if (!tempQueues.isEmpty()) {
// Remove any temporary queues
for (SimpleString queueName : tempQueues) {
if (!initialSession.isClosed()) {
try {
initialSession.deleteQueue(queueName);
}
catch (ActiveMQException ignore) {
// Exception on deleting queue shouldn't prevent close from completing
}
}
}
}
}
finally {
if (initialSession != null) {
initialSession.close();
}
}
closed = true;
}
catch (ActiveMQException e) {
throw JMSExceptionHelper.convertFromActiveMQException(e);
}
}
public ConnectionConsumer createConnectionConsumer(final Destination destination,
final String messageSelector,
final ServerSessionPool sessionPool,
final int maxMessages) throws JMSException {
checkClosed();
checkTempQueues(destination);
// We offer a RA, so no need to implement this for MDBs
return null;
}
private void checkTempQueues(Destination destination) throws JMSException {
ActiveMQDestination jbdest = (ActiveMQDestination) destination;
if (jbdest.isTemporary() && !containsTemporaryQueue(jbdest.getSimpleAddress())) {
throw new JMSException("Can not create consumer for temporary destination " + destination +
" from another JMS connection");
}
}
public ConnectionConsumer createDurableConnectionConsumer(final Topic topic,
final String subscriptionName,
final String messageSelector,
final ServerSessionPool sessionPool,
final int maxMessages) throws JMSException {
checkClosed();
// As spec. section 4.11
if (connectionType == ActiveMQConnection.TYPE_QUEUE_CONNECTION) {
String msg = "Cannot create a durable connection consumer on a QueueConnection";
throw new javax.jms.IllegalStateException(msg);
}
checkTempQueues(topic);
// We offer RA, so no need for this
return null;
}
@Override
public Session createSession(int sessionMode) throws JMSException {
checkClosed();
return createSessionInternal(false, sessionMode == Session.SESSION_TRANSACTED, sessionMode, ActiveMQSession.TYPE_GENERIC_SESSION);
}
@Override
public Session createSession() throws JMSException {
checkClosed();
return createSessionInternal(false, false, Session.AUTO_ACKNOWLEDGE, ActiveMQSession.TYPE_GENERIC_SESSION);
}
// QueueConnection implementation ---------------------------------------------------------------
public QueueSession createQueueSession(final boolean transacted, int acknowledgeMode) throws JMSException {
checkClosed();
return createSessionInternal(false, transacted, checkAck(transacted, acknowledgeMode), ActiveMQSession.TYPE_QUEUE_SESSION);
}
/**
* I'm keeping this as static as the same check will be done within RA.
* This is to conform with TCK Tests where we must return ackMode exactly as they want if transacted=false
*/
public static int checkAck(boolean transacted, int acknowledgeMode) {
if (!transacted && acknowledgeMode == Session.SESSION_TRANSACTED) {
return Session.AUTO_ACKNOWLEDGE;
}
return acknowledgeMode;
}
public ConnectionConsumer createConnectionConsumer(final Queue queue,
final String messageSelector,
final ServerSessionPool sessionPool,
final int maxMessages) throws JMSException {
checkClosed();
checkTempQueues(queue);
return null;
}
// TopicConnection implementation ---------------------------------------------------------------
public TopicSession createTopicSession(final boolean transacted, final int acknowledgeMode) throws JMSException {
checkClosed();
return createSessionInternal(false, transacted, checkAck(transacted, acknowledgeMode), ActiveMQSession.TYPE_TOPIC_SESSION);
}
public ConnectionConsumer createConnectionConsumer(final Topic topic,
final String messageSelector,
final ServerSessionPool sessionPool,
final int maxMessages) throws JMSException {
checkClosed();
checkTempQueues(topic);
return null;
}
@Override
public ConnectionConsumer createSharedConnectionConsumer(Topic topic,
String subscriptionName,
String messageSelector,
ServerSessionPool sessionPool,
int maxMessages) throws JMSException {
return null; // we offer RA
}
@Override
public ConnectionConsumer createSharedDurableConnectionConsumer(Topic topic,
String subscriptionName,
String messageSelector,
ServerSessionPool sessionPool,
int maxMessages) throws JMSException {
return null; // we offer RA
}
// Public ---------------------------------------------------------------------------------------
/**
* Sets a FailureListener for the session which is notified if a failure occurs on the session.
*
* @param listener the listener to add
* @throws JMSException
*/
public void setFailoverListener(final FailoverEventListener listener) throws JMSException {
checkClosed();
justCreated = false;
this.failoverEventListener = listener;
}
/**
* @return {@link FailoverEventListener} the current failover event listener for this connection
* @throws JMSException
*/
public FailoverEventListener getFailoverListener() throws JMSException {
checkClosed();
justCreated = false;
return failoverEventListener;
}
public void addTemporaryQueue(final SimpleString queueAddress) {
tempQueues.add(queueAddress);
knownDestinations.add(queueAddress);
}
public void removeTemporaryQueue(final SimpleString queueAddress) {
tempQueues.remove(queueAddress);
}
public void addKnownDestination(final SimpleString address) {
knownDestinations.add(address);
}
public boolean containsKnownDestination(final SimpleString address) {
return knownDestinations.contains(address);
}
public boolean containsTemporaryQueue(final SimpleString queueAddress) {
return tempQueues.contains(queueAddress);
}
public boolean hasNoLocal() {
return hasNoLocal;
}
public void setHasNoLocal() {
hasNoLocal = true;
}
public SimpleString getUID() {
return uid;
}
public void removeSession(final ActiveMQSession session) {
sessions.remove(session);
}
public ClientSession getInitialSession() {
return initialSession;
}
// Package protected ----------------------------------------------------------------------------
// Protected ------------------------------------------------------------------------------------
// In case the user forgets to close the connection manually
@Override
protected final void finalize() throws Throwable {
if (!closed) {
ActiveMQJMSClientLogger.LOGGER.connectionLeftOpen(creationStack);
close();
}
}
protected boolean isXA() {
return false;
}
protected final ActiveMQSession createSessionInternal(final boolean isXA,
final boolean transacted,
int acknowledgeMode,
final int type) throws JMSException {
if (transacted) {
acknowledgeMode = Session.SESSION_TRANSACTED;
}
try {
ClientSession session;
if (acknowledgeMode == Session.SESSION_TRANSACTED) {
session = sessionFactory.createSession(username, password, isXA, false, false, sessionFactory.getServerLocator().isPreAcknowledge(), transactionBatchSize);
}
else if (acknowledgeMode == Session.AUTO_ACKNOWLEDGE) {
session = sessionFactory.createSession(username, password, isXA, true, true, sessionFactory.getServerLocator().isPreAcknowledge(), 0);
}
else if (acknowledgeMode == Session.DUPS_OK_ACKNOWLEDGE) {
session = sessionFactory.createSession(username, password, isXA, true, true, sessionFactory.getServerLocator().isPreAcknowledge(), dupsOKBatchSize);
}
else if (acknowledgeMode == Session.CLIENT_ACKNOWLEDGE) {
session = sessionFactory.createSession(username, password, isXA, true, false, sessionFactory.getServerLocator().isPreAcknowledge(), transactionBatchSize);
}
else if (acknowledgeMode == ActiveMQJMSConstants.INDIVIDUAL_ACKNOWLEDGE) {
session = sessionFactory.createSession(username, password, isXA, true, false, false, transactionBatchSize);
}
else if (acknowledgeMode == ActiveMQJMSConstants.PRE_ACKNOWLEDGE) {
session = sessionFactory.createSession(username, password, isXA, true, false, true, transactionBatchSize);
}
else {
throw new JMSRuntimeException("Invalid ackmode: " + acknowledgeMode);
}
justCreated = false;
// Setting multiple times on different sessions doesn't matter since RemotingConnection
// maintains
// a set (no duplicates)
session.addFailureListener(listener);
session.addFailoverListener(failoverListener);
ActiveMQSession jbs = createAMQSession(isXA, transacted, acknowledgeMode, session, type);
sessions.add(jbs);
if (started) {
session.start();
}
this.addSessionMetaData(session);
return jbs;
}
catch (ActiveMQException e) {
throw JMSExceptionHelper.convertFromActiveMQException(e);
}
}
// Private --------------------------------------------------------------------------------------
/**
* @param transacted
* @param acknowledgeMode
* @param session
* @param type
* @return
*/
protected ActiveMQSession createAMQSession(boolean isXA,
boolean transacted,
int acknowledgeMode,
ClientSession session,
int type) {
if (isXA) {
return new ActiveMQXASession(this, transacted, true, acknowledgeMode, session, type);
}
else {
return new ActiveMQSession(this, transacted, false, acknowledgeMode, session, type);
}
}
protected final void checkClosed() throws JMSException {
if (closed) {
throw new IllegalStateException("Connection is closed");
}
}
public void authorize() throws JMSException {
try {
initialSession = sessionFactory.createSession(username, password, false, false, false, false, 0);
addSessionMetaData(initialSession);
initialSession.addFailureListener(listener);
initialSession.addFailoverListener(failoverListener);
}
catch (ActiveMQException me) {
throw JMSExceptionHelper.convertFromActiveMQException(me);
}
}
private void addSessionMetaData(ClientSession session) throws ActiveMQException {
session.addMetaData(ClientSession.JMS_SESSION_IDENTIFIER_PROPERTY, "");
if (clientID != null) {
session.addMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY, clientID);
}
}
public void setReference(ActiveMQConnectionFactory factory) {
this.factoryReference = factory;
}
public boolean isStarted() {
return started;
}
// Inner classes --------------------------------------------------------------------------------
private static class JMSFailureListener implements SessionFailureListener {
private final WeakReference connectionRef;
JMSFailureListener(final ActiveMQConnection connection) {
connectionRef = new WeakReference(connection);
}
@Override
public synchronized void connectionFailed(final ActiveMQException me, boolean failedOver) {
if (me == null) {
return;
}
ActiveMQConnection conn = connectionRef.get();
if (conn != null) {
try {
final ExceptionListener exceptionListener = conn.getExceptionListener();
if (exceptionListener != null) {
final JMSException je = new JMSException(me.toString(), failedOver ? EXCEPTION_FAILOVER : EXCEPTION_DISCONNECT);
je.initCause(me);
new Thread(new Runnable() {
public void run() {
exceptionListener.onException(je);
}
}).start();
}
}
catch (JMSException e) {
if (!conn.closed) {
ActiveMQJMSClientLogger.LOGGER.errorCallingExcListener(e);
}
}
}
}
@Override
public void connectionFailed(final ActiveMQException me, boolean failedOver, String scaleDownTargetNodeID) {
connectionFailed(me, failedOver);
}
public void beforeReconnect(final ActiveMQException me) {
}
}
private static class FailoverEventListenerImpl implements FailoverEventListener {
private final WeakReference connectionRef;
FailoverEventListenerImpl(final ActiveMQConnection connection) {
connectionRef = new WeakReference(connection);
}
@Override
public void failoverEvent(final FailoverEventType eventType) {
ActiveMQConnection conn = connectionRef.get();
if (conn != null) {
try {
final FailoverEventListener failoverListener = conn.getFailoverListener();
if (failoverListener != null) {
new Thread(new Runnable() {
public void run() {
failoverListener.failoverEvent(eventType);
}
}).start();
}
}
catch (JMSException e) {
if (!conn.closed) {
ActiveMQJMSClientLogger.LOGGER.errorCallingFailoverListener(e);
}
}
}
}
}
}