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

org.springframework.jms.connection.JmsResourceHolder Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2002-2020 the original author or authors.
 *
 * 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
 *
 *      https://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.springframework.jms.connection;

import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;

import jakarta.jms.Connection;
import jakarta.jms.ConnectionFactory;
import jakarta.jms.JMSException;
import jakarta.jms.Session;
import jakarta.jms.TransactionInProgressException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.lang.Nullable;
import org.springframework.transaction.support.ResourceHolderSupport;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

/**
 * Resource holder wrapping a JMS {@link Connection} and a JMS {@link Session}.
 * {@link JmsTransactionManager} binds instances of this class to the thread,
 * for a given JMS {@link ConnectionFactory}.
 *
 * 

Note: This is an SPI class, not intended to be used by applications. * * @author Juergen Hoeller * @since 1.1 * @see JmsTransactionManager * @see org.springframework.jms.core.JmsTemplate */ public class JmsResourceHolder extends ResourceHolderSupport { private static final Log logger = LogFactory.getLog(JmsResourceHolder.class); @Nullable private ConnectionFactory connectionFactory; private boolean frozen = false; private final Deque connections = new ArrayDeque<>(); private final Deque sessions = new ArrayDeque<>(); private final Map> sessionsPerConnection = new HashMap<>(); /** * Create a new JmsResourceHolder that is open for resources to be added. * @see #addConnection * @see #addSession */ public JmsResourceHolder() { } /** * Create a new JmsResourceHolder that is open for resources to be added. * @param connectionFactory the JMS ConnectionFactory that this * resource holder is associated with (may be {@code null}) */ public JmsResourceHolder(@Nullable ConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; } /** * Create a new JmsResourceHolder for the given JMS Session. * @param session the JMS Session */ public JmsResourceHolder(Session session) { addSession(session); this.frozen = true; } /** * Create a new JmsResourceHolder for the given JMS resources. * @param connection the JMS Connection * @param session the JMS Session */ public JmsResourceHolder(Connection connection, Session session) { addConnection(connection); addSession(session, connection); this.frozen = true; } /** * Create a new JmsResourceHolder for the given JMS resources. * @param connectionFactory the JMS ConnectionFactory that this * resource holder is associated with (may be {@code null}) * @param connection the JMS Connection * @param session the JMS Session */ public JmsResourceHolder(@Nullable ConnectionFactory connectionFactory, Connection connection, Session session) { this.connectionFactory = connectionFactory; addConnection(connection); addSession(session, connection); this.frozen = true; } /** * Return whether this resource holder is frozen, i.e. does not * allow for adding further Connections and Sessions to it. * @see #addConnection * @see #addSession */ public final boolean isFrozen() { return this.frozen; } /** * Add the given Connection to this resource holder. */ public final void addConnection(Connection connection) { Assert.isTrue(!this.frozen, "Cannot add Connection because JmsResourceHolder is frozen"); Assert.notNull(connection, "Connection must not be null"); if (!this.connections.contains(connection)) { this.connections.add(connection); } } /** * Add the given Session to this resource holder. */ public final void addSession(Session session) { addSession(session, null); } /** * Add the given Session to this resource holder, * registered for a specific Connection. */ public final void addSession(Session session, @Nullable Connection connection) { Assert.isTrue(!this.frozen, "Cannot add Session because JmsResourceHolder is frozen"); Assert.notNull(session, "Session must not be null"); if (!this.sessions.contains(session)) { this.sessions.add(session); if (connection != null) { Deque sessions = this.sessionsPerConnection.computeIfAbsent(connection, k -> new ArrayDeque<>()); sessions.add(session); } } } /** * Determine whether the given Session is registered * with this resource holder. */ public boolean containsSession(Session session) { return this.sessions.contains(session); } /** * Return this resource holder's default Connection, * or {@code null} if none. */ @Nullable public Connection getConnection() { return this.connections.peek(); } /** * Return this resource holder's Connection of the given type, * or {@code null} if none. */ @Nullable public C getConnection(Class connectionType) { return CollectionUtils.findValueOfType(this.connections, connectionType); } /** * Return an existing original Session, if any. *

In contrast to {@link #getSession()}, this must not lazily initialize * a new Session, not even in {@link JmsResourceHolder} subclasses. */ @Nullable Session getOriginalSession() { return this.sessions.peek(); } /** * Return this resource holder's default Session, * or {@code null} if none. */ @Nullable public Session getSession() { return this.sessions.peek(); } /** * Return this resource holder's Session of the given type, * or {@code null} if none. */ @Nullable public S getSession(Class sessionType) { return getSession(sessionType, null); } /** * Return this resource holder's Session of the given type * for the given connection, or {@code null} if none. */ @Nullable @SuppressWarnings("NullAway") public S getSession(Class sessionType, @Nullable Connection connection) { Deque sessions = (connection != null ? this.sessionsPerConnection.get(connection) : this.sessions); return CollectionUtils.findValueOfType(sessions, sessionType); } /** * Commit all of this resource holder's Sessions. * @throws JMSException if thrown from a Session commit attempt * @see Session#commit() */ public void commitAll() throws JMSException { for (Session session : this.sessions) { try { session.commit(); } catch (TransactionInProgressException ex) { // Ignore -> can only happen in case of a JTA transaction. } catch (jakarta.jms.IllegalStateException ex) { if (this.connectionFactory != null) { try { Method getDataSourceMethod = this.connectionFactory.getClass().getMethod("getDataSource"); Object ds = ReflectionUtils.invokeMethod(getDataSourceMethod, this.connectionFactory); while (ds != null) { if (TransactionSynchronizationManager.hasResource(ds)) { // IllegalStateException from sharing the underlying JDBC Connection // which typically gets committed first, for example, with Oracle AQ --> ignore return; } try { // Check for decorated DataSource a la Spring's DelegatingDataSource Method getTargetDataSourceMethod = ds.getClass().getMethod("getTargetDataSource"); ds = ReflectionUtils.invokeMethod(getTargetDataSourceMethod, ds); } catch (NoSuchMethodException nsme) { ds = null; } } } catch (Throwable ex2) { if (logger.isDebugEnabled()) { logger.debug("No working getDataSource method found on ConnectionFactory: " + ex2); } // No working getDataSource method - cannot perform DataSource transaction check } } throw ex; } } } /** * Close all of this resource holder's Sessions and clear its state. * @see Session#close() */ public void closeAll() { for (Session session : this.sessions) { try { session.close(); } catch (Throwable ex) { logger.debug("Could not close synchronized JMS Session after transaction", ex); } } for (Connection con : this.connections) { ConnectionFactoryUtils.releaseConnection(con, this.connectionFactory, true); } this.connections.clear(); this.sessions.clear(); this.sessionsPerConnection.clear(); } }