001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jms.connection;
018
019import java.lang.reflect.Method;
020import java.util.HashMap;
021import java.util.LinkedList;
022import java.util.List;
023import java.util.Map;
024import javax.jms.Connection;
025import javax.jms.ConnectionFactory;
026import javax.jms.JMSException;
027import javax.jms.Session;
028import javax.jms.TransactionInProgressException;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032
033import org.springframework.transaction.support.ResourceHolderSupport;
034import org.springframework.transaction.support.TransactionSynchronizationManager;
035import org.springframework.util.Assert;
036import org.springframework.util.CollectionUtils;
037import org.springframework.util.ReflectionUtils;
038
039/**
040 * JMS resource holder, wrapping a JMS Connection and a JMS Session.
041 * JmsTransactionManager binds instances of this class to the thread,
042 * for a given JMS ConnectionFactory.
043 *
044 * <p>Note: This is an SPI class, not intended to be used by applications.
045 *
046 * @author Juergen Hoeller
047 * @since 1.1
048 * @see JmsTransactionManager
049 * @see org.springframework.jms.core.JmsTemplate
050 */
051public class JmsResourceHolder extends ResourceHolderSupport {
052
053        private static final Log logger = LogFactory.getLog(JmsResourceHolder.class);
054
055        private ConnectionFactory connectionFactory;
056
057        private boolean frozen = false;
058
059        private final List<Connection> connections = new LinkedList<Connection>();
060
061        private final List<Session> sessions = new LinkedList<Session>();
062
063        private final Map<Connection, List<Session>> sessionsPerConnection =
064                        new HashMap<Connection, List<Session>>();
065
066
067        /**
068         * Create a new JmsResourceHolder that is open for resources to be added.
069         * @see #addConnection
070         * @see #addSession
071         */
072        public JmsResourceHolder() {
073        }
074
075        /**
076         * Create a new JmsResourceHolder that is open for resources to be added.
077         * @param connectionFactory the JMS ConnectionFactory that this
078         * resource holder is associated with (may be {@code null})
079         */
080        public JmsResourceHolder(ConnectionFactory connectionFactory) {
081                this.connectionFactory = connectionFactory;
082        }
083
084        /**
085         * Create a new JmsResourceHolder for the given JMS Session.
086         * @param session the JMS Session
087         */
088        public JmsResourceHolder(Session session) {
089                addSession(session);
090                this.frozen = true;
091        }
092
093        /**
094         * Create a new JmsResourceHolder for the given JMS resources.
095         * @param connection the JMS Connection
096         * @param session the JMS Session
097         */
098        public JmsResourceHolder(Connection connection, Session session) {
099                addConnection(connection);
100                addSession(session, connection);
101                this.frozen = true;
102        }
103
104        /**
105         * Create a new JmsResourceHolder for the given JMS resources.
106         * @param connectionFactory the JMS ConnectionFactory that this
107         * resource holder is associated with (may be {@code null})
108         * @param connection the JMS Connection
109         * @param session the JMS Session
110         */
111        public JmsResourceHolder(ConnectionFactory connectionFactory, Connection connection, Session session) {
112                this.connectionFactory = connectionFactory;
113                addConnection(connection);
114                addSession(session, connection);
115                this.frozen = true;
116        }
117
118
119        public final boolean isFrozen() {
120                return this.frozen;
121        }
122
123        public final void addConnection(Connection connection) {
124                Assert.isTrue(!this.frozen, "Cannot add Connection because JmsResourceHolder is frozen");
125                Assert.notNull(connection, "Connection must not be null");
126                if (!this.connections.contains(connection)) {
127                        this.connections.add(connection);
128                }
129        }
130
131        public final void addSession(Session session) {
132                addSession(session, null);
133        }
134
135        public final void addSession(Session session, Connection connection) {
136                Assert.isTrue(!this.frozen, "Cannot add Session because JmsResourceHolder is frozen");
137                Assert.notNull(session, "Session must not be null");
138                if (!this.sessions.contains(session)) {
139                        this.sessions.add(session);
140                        if (connection != null) {
141                                List<Session> sessions = this.sessionsPerConnection.get(connection);
142                                if (sessions == null) {
143                                        sessions = new LinkedList<Session>();
144                                        this.sessionsPerConnection.put(connection, sessions);
145                                }
146                                sessions.add(session);
147                        }
148                }
149        }
150
151        public boolean containsSession(Session session) {
152                return this.sessions.contains(session);
153        }
154
155
156        public Connection getConnection() {
157                return (!this.connections.isEmpty() ? this.connections.get(0) : null);
158        }
159
160        public Connection getConnection(Class<? extends Connection> connectionType) {
161                return CollectionUtils.findValueOfType(this.connections, connectionType);
162        }
163
164        public Session getSession() {
165                return (!this.sessions.isEmpty() ? this.sessions.get(0) : null);
166        }
167
168        public Session getSession(Class<? extends Session> sessionType) {
169                return getSession(sessionType, null);
170        }
171
172        public Session getSession(Class<? extends Session> sessionType, Connection connection) {
173                List<Session> sessions = (connection != null ? this.sessionsPerConnection.get(connection) : this.sessions);
174                return CollectionUtils.findValueOfType(sessions, sessionType);
175        }
176
177
178        public void commitAll() throws JMSException {
179                for (Session session : this.sessions) {
180                        try {
181                                session.commit();
182                        }
183                        catch (TransactionInProgressException ex) {
184                                // Ignore -> can only happen in case of a JTA transaction.
185                        }
186                        catch (javax.jms.IllegalStateException ex) {
187                                if (this.connectionFactory != null) {
188                                        try {
189                                                Method getDataSourceMethod = this.connectionFactory.getClass().getMethod("getDataSource");
190                                                Object ds = ReflectionUtils.invokeMethod(getDataSourceMethod, this.connectionFactory);
191                                                while (ds != null) {
192                                                        if (TransactionSynchronizationManager.hasResource(ds)) {
193                                                                // IllegalStateException from sharing the underlying JDBC Connection
194                                                                // which typically gets committed first, e.g. with Oracle AQ --> ignore
195                                                                return;
196                                                        }
197                                                        try {
198                                                                // Check for decorated DataSource a la Spring's DelegatingDataSource
199                                                                Method getTargetDataSourceMethod = ds.getClass().getMethod("getTargetDataSource");
200                                                                ds = ReflectionUtils.invokeMethod(getTargetDataSourceMethod, ds);
201                                                        }
202                                                        catch (NoSuchMethodException nsme) {
203                                                                ds = null;
204                                                        }
205                                                }
206                                        }
207                                        catch (Throwable ex2) {
208                                                if (logger.isDebugEnabled()) {
209                                                        logger.debug("No working getDataSource method found on ConnectionFactory: " + ex2);
210                                                }
211                                                // No working getDataSource method - cannot perform DataSource transaction check
212                                        }
213                                }
214                                throw ex;
215                        }
216                }
217        }
218
219        public void closeAll() {
220                for (Session session : this.sessions) {
221                        try {
222                                session.close();
223                        }
224                        catch (Throwable ex) {
225                                logger.debug("Could not close synchronized JMS Session after transaction", ex);
226                        }
227                }
228                for (Connection con : this.connections) {
229                        ConnectionFactoryUtils.releaseConnection(con, this.connectionFactory, true);
230                }
231                this.connections.clear();
232                this.sessions.clear();
233                this.sessionsPerConnection.clear();
234        }
235
236}