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}