001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.activemq.jms.pool; 019 020import java.util.List; 021import java.util.concurrent.CopyOnWriteArrayList; 022import java.util.concurrent.atomic.AtomicBoolean; 023 024import javax.jms.*; 025import javax.jms.IllegalStateException; 026 027import org.apache.commons.pool.KeyedPoolableObjectFactory; 028import org.apache.commons.pool.impl.GenericKeyedObjectPool; 029import org.apache.commons.pool.impl.GenericObjectPool; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * Holds a real JMS connection along with the session pools associated with it. 035 * <p/> 036 * Instances of this class are shared amongst one or more PooledConnection object and must 037 * track the session objects that are loaned out for cleanup on close as well as ensuring 038 * that the temporary destinations of the managed Connection are purged when all references 039 * to this ConnectionPool are released. 040 */ 041public class ConnectionPool implements ExceptionListener { 042 private static final transient Logger LOG = LoggerFactory.getLogger(ConnectionPool.class); 043 044 protected Connection connection; 045 private int referenceCount; 046 private long lastUsed = System.currentTimeMillis(); 047 private final long firstUsed = lastUsed; 048 private boolean hasExpired; 049 private int idleTimeout = 30 * 1000; 050 private long expiryTimeout = 0l; 051 private boolean useAnonymousProducers = true; 052 053 private final AtomicBoolean started = new AtomicBoolean(false); 054 private final GenericKeyedObjectPool<SessionKey, Session> sessionPool; 055 private final List<PooledSession> loanedSessions = new CopyOnWriteArrayList<PooledSession>(); 056 private boolean reconnectOnException; 057 private ExceptionListener parentExceptionListener; 058 059 public ConnectionPool(Connection connection) { 060 061 this.connection = wrap(connection); 062 063 // Create our internal Pool of session instances. 064 this.sessionPool = new GenericKeyedObjectPool<SessionKey, Session>( 065 new KeyedPoolableObjectFactory<SessionKey, Session>() { 066 067 @Override 068 public void activateObject(SessionKey key, Session session) throws Exception { 069 } 070 071 @Override 072 public void destroyObject(SessionKey key, Session session) throws Exception { 073 session.close(); 074 } 075 076 @Override 077 public Session makeObject(SessionKey key) throws Exception { 078 return makeSession(key); 079 } 080 081 @Override 082 public void passivateObject(SessionKey key, Session session) throws Exception { 083 } 084 085 @Override 086 public boolean validateObject(SessionKey key, Session session) { 087 return true; 088 } 089 } 090 ); 091 } 092 093 // useful when external failure needs to force expiry 094 public void setHasExpired(boolean val) { 095 hasExpired = val; 096 } 097 098 protected Session makeSession(SessionKey key) throws JMSException { 099 return connection.createSession(key.isTransacted(), key.getAckMode()); 100 } 101 102 protected Connection wrap(Connection connection) { 103 return connection; 104 } 105 106 protected void unWrap(Connection connection) { 107 } 108 109 public void start() throws JMSException { 110 if (started.compareAndSet(false, true)) { 111 try { 112 connection.start(); 113 } catch (JMSException e) { 114 started.set(false); 115 throw(e); 116 } 117 } 118 } 119 120 public synchronized Connection getConnection() { 121 return connection; 122 } 123 124 public Session createSession(boolean transacted, int ackMode) throws JMSException { 125 SessionKey key = new SessionKey(transacted, ackMode); 126 PooledSession session; 127 try { 128 session = new PooledSession(key, sessionPool.borrowObject(key), sessionPool, key.isTransacted(), useAnonymousProducers); 129 session.addSessionEventListener(new PooledSessionEventListener() { 130 131 @Override 132 public void onTemporaryTopicCreate(TemporaryTopic tempTopic) { 133 } 134 135 @Override 136 public void onTemporaryQueueCreate(TemporaryQueue tempQueue) { 137 } 138 139 @Override 140 public void onSessionClosed(PooledSession session) { 141 ConnectionPool.this.loanedSessions.remove(session); 142 } 143 }); 144 this.loanedSessions.add(session); 145 } catch (Exception e) { 146 IllegalStateException illegalStateException = new IllegalStateException(e.toString()); 147 illegalStateException.initCause(e); 148 throw illegalStateException; 149 } 150 return session; 151 } 152 153 public synchronized void close() { 154 if (connection != null) { 155 try { 156 sessionPool.close(); 157 } catch (Exception e) { 158 } finally { 159 try { 160 connection.close(); 161 } catch (Exception e) { 162 } finally { 163 connection = null; 164 } 165 } 166 } 167 } 168 169 public synchronized void incrementReferenceCount() { 170 referenceCount++; 171 lastUsed = System.currentTimeMillis(); 172 } 173 174 public synchronized void decrementReferenceCount() { 175 referenceCount--; 176 lastUsed = System.currentTimeMillis(); 177 if (referenceCount == 0) { 178 // Loaned sessions are those that are active in the sessionPool and 179 // have not been closed by the client before closing the connection. 180 // These need to be closed so that all session's reflect the fact 181 // that the parent Connection is closed. 182 for (PooledSession session : this.loanedSessions) { 183 try { 184 session.close(); 185 } catch (Exception e) { 186 } 187 } 188 this.loanedSessions.clear(); 189 190 unWrap(getConnection()); 191 192 expiredCheck(); 193 } 194 } 195 196 /** 197 * Determines if this Connection has expired. 198 * <p/> 199 * A ConnectionPool is considered expired when all references to it are released AND either 200 * the configured idleTimeout has elapsed OR the configured expiryTimeout has elapsed. 201 * Once a ConnectionPool is determined to have expired its underlying Connection is closed. 202 * 203 * @return true if this connection has expired. 204 */ 205 public synchronized boolean expiredCheck() { 206 207 boolean expired = false; 208 209 if (connection == null) { 210 return true; 211 } 212 213 if (hasExpired) { 214 if (referenceCount == 0) { 215 close(); 216 expired = true; 217 } 218 } 219 220 if (expiryTimeout > 0 && System.currentTimeMillis() > firstUsed + expiryTimeout) { 221 hasExpired = true; 222 if (referenceCount == 0) { 223 close(); 224 expired = true; 225 } 226 } 227 228 // Only set hasExpired here is no references, as a Connection with references is by 229 // definition not idle at this time. 230 if (referenceCount == 0 && idleTimeout > 0 && System.currentTimeMillis() > lastUsed + idleTimeout) { 231 hasExpired = true; 232 close(); 233 expired = true; 234 } 235 236 return expired; 237 } 238 239 public int getIdleTimeout() { 240 return idleTimeout; 241 } 242 243 public void setIdleTimeout(int idleTimeout) { 244 this.idleTimeout = idleTimeout; 245 } 246 247 public void setExpiryTimeout(long expiryTimeout) { 248 this.expiryTimeout = expiryTimeout; 249 } 250 251 public long getExpiryTimeout() { 252 return expiryTimeout; 253 } 254 255 public int getMaximumActiveSessionPerConnection() { 256 return this.sessionPool.getMaxActive(); 257 } 258 259 public void setMaximumActiveSessionPerConnection(int maximumActiveSessionPerConnection) { 260 this.sessionPool.setMaxActive(maximumActiveSessionPerConnection); 261 } 262 263 public boolean isUseAnonymousProducers() { 264 return this.useAnonymousProducers; 265 } 266 267 public void setUseAnonymousProducers(boolean value) { 268 this.useAnonymousProducers = value; 269 } 270 271 /** 272 * @return the total number of Pooled session including idle sessions that are not 273 * currently loaned out to any client. 274 */ 275 public int getNumSessions() { 276 return this.sessionPool.getNumIdle() + this.sessionPool.getNumActive(); 277 } 278 279 /** 280 * @return the total number of Sessions that are in the Session pool but not loaned out. 281 */ 282 public int getNumIdleSessions() { 283 return this.sessionPool.getNumIdle(); 284 } 285 286 /** 287 * @return the total number of Session's that have been loaned to PooledConnection instances. 288 */ 289 public int getNumActiveSessions() { 290 return this.sessionPool.getNumActive(); 291 } 292 293 /** 294 * Configure whether the createSession method should block when there are no more idle sessions and the 295 * pool already contains the maximum number of active sessions. If false the create method will fail 296 * and throw an exception. 297 * 298 * @param block 299 * Indicates whether blocking should be used to wait for more space to create a session. 300 */ 301 public void setBlockIfSessionPoolIsFull(boolean block) { 302 this.sessionPool.setWhenExhaustedAction( 303 (block ? GenericObjectPool.WHEN_EXHAUSTED_BLOCK : GenericObjectPool.WHEN_EXHAUSTED_FAIL)); 304 } 305 306 public boolean isBlockIfSessionPoolIsFull() { 307 return this.sessionPool.getWhenExhaustedAction() == GenericObjectPool.WHEN_EXHAUSTED_BLOCK; 308 } 309 310 /** 311 * Returns the timeout to use for blocking creating new sessions 312 * 313 * @return true if the pooled Connection createSession method will block when the limit is hit. 314 * @see #setBlockIfSessionPoolIsFull(boolean) 315 */ 316 public long getBlockIfSessionPoolIsFullTimeout() { 317 return this.sessionPool.getMaxWait(); 318 } 319 320 /** 321 * Controls the behavior of the internal session pool. By default the call to 322 * Connection.getSession() will block if the session pool is full. This setting 323 * will affect how long it blocks and throws an exception after the timeout. 324 * 325 * The size of the session pool is controlled by the @see #maximumActive 326 * property. 327 * 328 * Whether or not the call to create session blocks is controlled by the @see #blockIfSessionPoolIsFull 329 * property 330 * 331 * @param blockIfSessionPoolIsFullTimeout - if blockIfSessionPoolIsFullTimeout is true, 332 * then use this setting to configure how long to block before retry 333 */ 334 public void setBlockIfSessionPoolIsFullTimeout(long blockIfSessionPoolIsFullTimeout) { 335 this.sessionPool.setMaxWait(blockIfSessionPoolIsFullTimeout); 336 } 337 338 /** 339 * @return true if the underlying connection will be renewed on JMSException, false otherwise 340 */ 341 public boolean isReconnectOnException() { 342 return reconnectOnException; 343 } 344 345 /** 346 * Controls weather the underlying connection should be reset (and renewed) on JMSException 347 * 348 * @param reconnectOnException 349 * Boolean value that configures whether reconnect on exception should happen 350 */ 351 public void setReconnectOnException(boolean reconnectOnException) { 352 this.reconnectOnException = reconnectOnException; 353 try { 354 if (isReconnectOnException()) { 355 if (connection.getExceptionListener() != null) { 356 parentExceptionListener = connection.getExceptionListener(); 357 } 358 connection.setExceptionListener(this); 359 } else { 360 if (parentExceptionListener != null) { 361 connection.setExceptionListener(parentExceptionListener); 362 } 363 parentExceptionListener = null; 364 } 365 } catch (JMSException jmse) { 366 LOG.warn("Cannot set reconnect exception listener", jmse); 367 } 368 } 369 370 @Override 371 public void onException(JMSException exception) { 372 close(); 373 if (parentExceptionListener != null) { 374 parentExceptionListener.onException(exception); 375 } 376 } 377 378 @Override 379 public String toString() { 380 return "ConnectionPool[" + connection + "]"; 381 } 382}