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