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 java.lang.ref.WeakReference;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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 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.ActiveMQThreadFactory;
import org.apache.activemq.artemis.utils.UUIDGenerator;
import org.apache.activemq.artemis.utils.VersionLoader;
import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet;
/**
* 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 {
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;
private final int connectionType;
private final Set sessions = new ConcurrentHashSet<>();
private final Set tempQueues = 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 ExecutorService failoverListenerExecutor = Executors.newFixedThreadPool(1, ActiveMQThreadFactory.defaultThreadFactory(getClass().getName()));
private final Version thisVersion;
private final int dupsOKBatchSize;
private final int transactionBatchSize;
private final boolean cacheDestinations;
private final boolean enable1xPrefixes;
private ClientSession initialSession;
private final Exception creationStack;
private ActiveMQConnectionFactory factoryReference;
private final ConnectionFactoryOptions options;
public ActiveMQConnection(final ConnectionFactoryOptions options,
final String username,
final String password,
final int connectionType,
final String clientID,
final int dupsOKBatchSize,
final int transactionBatchSize,
final boolean cacheDestinations,
final boolean enable1xPrefixes,
final ClientSessionFactory sessionFactory) {
this.options = options;
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;
this.cacheDestinations = cacheDestinations;
this.enable1xPrefixes = enable1xPrefixes;
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 synchronized 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 synchronized 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 synchronized Session createNonXAQueueSession(final boolean transacted, final int acknowledgeMode) throws JMSException {
checkClosed();
return createSessionInternal(false, transacted, acknowledgeMode, ActiveMQConnection.TYPE_QUEUE_CONNECTION);
}
// Connection implementation --------------------------------------------------------------------
@Override
public synchronized Session createSession(final boolean transacted, final int acknowledgeMode) throws JMSException {
checkClosed();
return createSessionInternal(false, transacted, checkAck(transacted, acknowledgeMode), ActiveMQConnection.TYPE_GENERIC_CONNECTION);
}
@Override
public String getClientID() throws JMSException {
checkClosed();
return clientID;
}
@Override
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 {
validateClientID(initialSession, clientID);
this.clientID = clientID;
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;
}
private void validateClientID(ClientSession validateSession, String clientID)
throws InvalidClientIDException, ActiveMQException {
try {
validateSession.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");
} else {
throw e;
}
}
}
@Override
public ConnectionMetaData getMetaData() throws JMSException {
checkClosed();
if (metaData == null) {
metaData = new ActiveMQConnectionMetaData(thisVersion);
}
return metaData;
}
@Override
public ExceptionListener getExceptionListener() throws JMSException {
checkClosed();
return exceptionListener;
}
@Override
public void setExceptionListener(final ExceptionListener listener) throws JMSException {
checkClosed();
exceptionListener = listener;
}
@Override
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();
}
}
}
@Override
public synchronized void stop() throws JMSException {
threadAwareContext.assertNotMessageListenerThread();
checkClosed();
for (ActiveMQSession session : sessions) {
session.stop();
}
started = false;
}
@Override
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();
}
}
AccessController.doPrivileged((PrivilegedAction