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 */ 017package org.apache.activemq.jms.pool; 018 019import java.util.Properties; 020import java.util.concurrent.atomic.AtomicBoolean; 021import java.util.concurrent.atomic.AtomicReference; 022 023import javax.jms.Connection; 024import javax.jms.ConnectionFactory; 025import javax.jms.JMSException; 026import javax.jms.QueueConnection; 027import javax.jms.QueueConnectionFactory; 028import javax.jms.TopicConnection; 029import javax.jms.TopicConnectionFactory; 030 031import org.apache.commons.pool2.KeyedPooledObjectFactory; 032import org.apache.commons.pool2.PooledObject; 033import org.apache.commons.pool2.impl.DefaultPooledObject; 034import org.apache.commons.pool2.impl.GenericKeyedObjectPool; 035import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039/** 040 * A JMS provider which pools Connection, Session and MessageProducer instances 041 * so it can be used with tools like <a href="http://camel.apache.org/activemq.html">Camel</a> and Spring's 042 * <a href="http://activemq.apache.org/spring-support.html">JmsTemplate and MessagListenerContainer</a>. 043 * Connections, sessions and producers are returned to a pool after use so that they can be reused later 044 * without having to undergo the cost of creating them again. 045 * 046 * b>NOTE:</b> while this implementation does allow the creation of a collection of active consumers, 047 * it does not 'pool' consumers. Pooling makes sense for connections, sessions and producers, which 048 * are expensive to create and can remain idle a minimal cost. Consumers, on the other hand, are usually 049 * just created at startup and left active, handling incoming messages as they come. When a consumer is 050 * complete, it is best to close it rather than return it to a pool for later reuse: this is because, 051 * even if a consumer is idle, ActiveMQ will keep delivering messages to the consumer's prefetch buffer, 052 * where they'll get held until the consumer is active again. 053 * 054 * If you are creating a collection of consumers (for example, for multi-threaded message consumption), you 055 * might want to consider using a lower prefetch value for each consumer (e.g. 10 or 20), to ensure that 056 * all messages don't end up going to just one of the consumers. See this FAQ entry for more detail: 057 * http://activemq.apache.org/i-do-not-receive-messages-in-my-second-consumer.html 058 * 059 * Optionally, one may configure the pool to examine and possibly evict objects as they sit idle in the 060 * pool. This is performed by an "idle object eviction" thread, which runs asynchronously. Caution should 061 * be used when configuring this optional feature. Eviction runs contend with client threads for access 062 * to objects in the pool, so if they run too frequently performance issues may result. The idle object 063 * eviction thread may be configured using the {@link org.apache.activemq.jms.pool.PooledConnectionFactory#setTimeBetweenExpirationCheckMillis} method. By 064 * default the value is -1 which means no eviction thread will be run. Set to a non-negative value to 065 * configure the idle eviction thread to run. 066 * 067 */ 068public class PooledConnectionFactory implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory { 069 private static final transient Logger LOG = LoggerFactory.getLogger(PooledConnectionFactory.class); 070 071 protected final AtomicBoolean stopped = new AtomicBoolean(false); 072 private GenericKeyedObjectPool<ConnectionKey, ConnectionPool> connectionsPool; 073 074 protected Object connectionFactory; 075 076 private int maximumActiveSessionPerConnection = 500; 077 private int idleTimeout = 30 * 1000; 078 private boolean blockIfSessionPoolIsFull = true; 079 private long blockIfSessionPoolIsFullTimeout = -1L; 080 private long expiryTimeout = 0l; 081 private boolean createConnectionOnStartup = true; 082 private boolean useAnonymousProducers = true; 083 private boolean reconnectOnException = true; 084 085 // Temporary value used to always fetch the result of makeObject. 086 private final AtomicReference<ConnectionPool> mostRecentlyCreated = new AtomicReference<ConnectionPool>(null); 087 088 public void initConnectionsPool() { 089 if (this.connectionsPool == null) { 090 final GenericKeyedObjectPoolConfig poolConfig = new GenericKeyedObjectPoolConfig(); 091 poolConfig.setJmxEnabled(false); 092 this.connectionsPool = new GenericKeyedObjectPool<ConnectionKey, ConnectionPool>( 093 new KeyedPooledObjectFactory<ConnectionKey, ConnectionPool>() { 094 @Override 095 public PooledObject<ConnectionPool> makeObject(ConnectionKey connectionKey) throws Exception { 096 Connection delegate = createConnection(connectionKey); 097 098 ConnectionPool connection = createConnectionPool(delegate); 099 connection.setIdleTimeout(getIdleTimeout()); 100 connection.setExpiryTimeout(getExpiryTimeout()); 101 connection.setMaximumActiveSessionPerConnection(getMaximumActiveSessionPerConnection()); 102 connection.setBlockIfSessionPoolIsFull(isBlockIfSessionPoolIsFull()); 103 if (isBlockIfSessionPoolIsFull() && getBlockIfSessionPoolIsFullTimeout() > 0) { 104 connection.setBlockIfSessionPoolIsFullTimeout(getBlockIfSessionPoolIsFullTimeout()); 105 } 106 connection.setUseAnonymousProducers(isUseAnonymousProducers()); 107 connection.setReconnectOnException(isReconnectOnException()); 108 109 if (LOG.isTraceEnabled()) { 110 LOG.trace("Created new connection: {}", connection); 111 } 112 113 PooledConnectionFactory.this.mostRecentlyCreated.set(connection); 114 115 return new DefaultPooledObject<ConnectionPool>(connection); 116 } 117 118 @Override 119 public void destroyObject(ConnectionKey connectionKey, PooledObject<ConnectionPool> pooledObject) throws Exception { 120 ConnectionPool connection = pooledObject.getObject(); 121 try { 122 if (LOG.isTraceEnabled()) { 123 LOG.trace("Destroying connection: {}", connection); 124 } 125 connection.close(); 126 } catch (Exception e) { 127 LOG.warn("Close connection failed for connection: " + connection + ". This exception will be ignored.",e); 128 } 129 } 130 131 @Override 132 public boolean validateObject(ConnectionKey connectionKey, PooledObject<ConnectionPool> pooledObject) { 133 ConnectionPool connection = pooledObject.getObject(); 134 if (connection != null && connection.expiredCheck()) { 135 if (LOG.isTraceEnabled()) { 136 LOG.trace("Connection has expired: {} and will be destroyed", connection); 137 } 138 139 return false; 140 } 141 142 return true; 143 } 144 145 @Override 146 public void activateObject(ConnectionKey connectionKey, PooledObject<ConnectionPool> pooledObject) throws Exception { 147 } 148 149 @Override 150 public void passivateObject(ConnectionKey connectionKey, PooledObject<ConnectionPool> pooledObject) throws Exception { 151 } 152 153 }, poolConfig); 154 155 // Set max idle (not max active) since our connections always idle in the pool. 156 this.connectionsPool.setMaxIdlePerKey(1); 157 this.connectionsPool.setLifo(false); 158 159 // We always want our validate method to control when idle objects are evicted. 160 this.connectionsPool.setTestOnBorrow(true); 161 this.connectionsPool.setTestWhileIdle(true); 162 } 163 } 164 165 /** 166 * @return the currently configured ConnectionFactory used to create the pooled Connections. 167 */ 168 public Object getConnectionFactory() { 169 return connectionFactory; 170 } 171 172 /** 173 * Sets the ConnectionFactory used to create new pooled Connections. 174 * <p/> 175 * Updates to this value do not affect Connections that were previously created and placed 176 * into the pool. In order to allocate new Connections based off this new ConnectionFactory 177 * it is first necessary to {@link #clear} the pooled Connections. 178 * 179 * @param toUse 180 * The factory to use to create pooled Connections. 181 */ 182 public void setConnectionFactory(final Object toUse) { 183 if (toUse instanceof ConnectionFactory) { 184 this.connectionFactory = toUse; 185 } else { 186 throw new IllegalArgumentException("connectionFactory should implement javax.jmx.ConnectionFactory"); 187 } 188 } 189 190 @Override 191 public QueueConnection createQueueConnection() throws JMSException { 192 return (QueueConnection) createConnection(); 193 } 194 195 @Override 196 public QueueConnection createQueueConnection(String userName, String password) throws JMSException { 197 return (QueueConnection) createConnection(userName, password); 198 } 199 200 @Override 201 public TopicConnection createTopicConnection() throws JMSException { 202 return (TopicConnection) createConnection(); 203 } 204 205 @Override 206 public TopicConnection createTopicConnection(String userName, String password) throws JMSException { 207 return (TopicConnection) createConnection(userName, password); 208 } 209 210 @Override 211 public Connection createConnection() throws JMSException { 212 return createConnection(null, null); 213 } 214 215 @Override 216 public synchronized Connection createConnection(String userName, String password) throws JMSException { 217 if (stopped.get()) { 218 LOG.debug("PooledConnectionFactory is stopped, skip create new connection."); 219 return null; 220 } 221 222 ConnectionPool connection = null; 223 ConnectionKey key = new ConnectionKey(userName, password); 224 225 // This will either return an existing non-expired ConnectionPool or it 226 // will create a new one to meet the demand. 227 if (getConnectionsPool().getNumIdle(key) < getMaxConnections()) { 228 try { 229 connectionsPool.addObject(key); 230 connection = mostRecentlyCreated.getAndSet(null); 231 connection.incrementReferenceCount(); 232 } catch (Exception e) { 233 throw createJmsException("Error while attempting to add new Connection to the pool", e); 234 } 235 } else { 236 try { 237 // We can race against other threads returning the connection when there is an 238 // expiration or idle timeout. We keep pulling out ConnectionPool instances until 239 // we win and get a non-closed instance and then increment the reference count 240 // under lock to prevent another thread from triggering an expiration check and 241 // pulling the rug out from under us. 242 while (connection == null) { 243 connection = connectionsPool.borrowObject(key); 244 synchronized (connection) { 245 if (connection.getConnection() != null) { 246 connection.incrementReferenceCount(); 247 break; 248 } 249 250 // Return the bad one to the pool and let if get destroyed as normal. 251 connectionsPool.returnObject(key, connection); 252 connection = null; 253 } 254 } 255 } catch (Exception e) { 256 throw createJmsException("Error while attempting to retrieve a connection from the pool", e); 257 } 258 259 try { 260 connectionsPool.returnObject(key, connection); 261 } catch (Exception e) { 262 throw createJmsException("Error when returning connection to the pool", e); 263 } 264 } 265 266 return newPooledConnection(connection); 267 } 268 269 protected Connection newPooledConnection(ConnectionPool connection) { 270 return new PooledConnection(connection); 271 } 272 273 private JMSException createJmsException(String msg, Exception cause) { 274 JMSException exception = new JMSException(msg); 275 exception.setLinkedException(cause); 276 exception.initCause(cause); 277 return exception; 278 } 279 280 protected Connection createConnection(ConnectionKey key) throws JMSException { 281 if (connectionFactory instanceof ConnectionFactory) { 282 if (key.getUserName() == null && key.getPassword() == null) { 283 return ((ConnectionFactory) connectionFactory).createConnection(); 284 } else { 285 return ((ConnectionFactory) connectionFactory).createConnection(key.getUserName(), key.getPassword()); 286 } 287 } else { 288 throw new IllegalStateException("connectionFactory should implement javax.jms.ConnectionFactory"); 289 } 290 } 291 292 public void start() { 293 LOG.debug("Staring the PooledConnectionFactory: create on start = {}", isCreateConnectionOnStartup()); 294 stopped.set(false); 295 if (isCreateConnectionOnStartup()) { 296 try { 297 // warm the pool by creating a connection during startup 298 createConnection().close(); 299 } catch (JMSException e) { 300 LOG.warn("Create pooled connection during start failed. This exception will be ignored.", e); 301 } 302 } 303 } 304 305 public void stop() { 306 if (stopped.compareAndSet(false, true)) { 307 LOG.debug("Stopping the PooledConnectionFactory, number of connections in cache: {}", 308 connectionsPool != null ? connectionsPool.getNumActive() : 0); 309 try { 310 if (connectionsPool != null) { 311 connectionsPool.close(); 312 } 313 } catch (Exception e) { 314 } 315 } 316 } 317 318 /** 319 * Clears all connections from the pool. Each connection that is currently in the pool is 320 * closed and removed from the pool. A new connection will be created on the next call to 321 * {@link #createConnection}. Care should be taken when using this method as Connections that 322 * are in use be client's will be closed. 323 */ 324 public void clear() { 325 326 if (stopped.get()) { 327 return; 328 } 329 330 getConnectionsPool().clear(); 331 } 332 333 /** 334 * Returns the currently configured maximum number of sessions a pooled Connection will 335 * create before it either blocks or throws an exception when a new session is requested, 336 * depending on configuration. 337 * 338 * @return the number of session instances that can be taken from a pooled connection. 339 */ 340 public int getMaximumActiveSessionPerConnection() { 341 return maximumActiveSessionPerConnection; 342 } 343 344 /** 345 * Sets the maximum number of active sessions per connection 346 * 347 * @param maximumActiveSessionPerConnection 348 * The maximum number of active session per connection in the pool. 349 */ 350 public void setMaximumActiveSessionPerConnection(int maximumActiveSessionPerConnection) { 351 this.maximumActiveSessionPerConnection = maximumActiveSessionPerConnection; 352 } 353 354 /** 355 * Controls the behavior of the internal session pool. By default the call to 356 * Connection.getSession() will block if the session pool is full. If the 357 * argument false is given, it will change the default behavior and instead the 358 * call to getSession() will throw a JMSException. 359 * 360 * The size of the session pool is controlled by the @see #maximumActive 361 * property. 362 * 363 * @param block - if true, the call to getSession() blocks if the pool is full 364 * until a session object is available. defaults to true. 365 */ 366 public void setBlockIfSessionPoolIsFull(boolean block) { 367 this.blockIfSessionPoolIsFull = block; 368 } 369 370 /** 371 * Returns whether a pooled Connection will enter a blocked state or will throw an Exception 372 * once the maximum number of sessions has been borrowed from the the Session Pool. 373 * 374 * @return true if the pooled Connection createSession method will block when the limit is hit. 375 * @see #setBlockIfSessionPoolIsFull(boolean) 376 */ 377 public boolean isBlockIfSessionPoolIsFull() { 378 return this.blockIfSessionPoolIsFull; 379 } 380 381 /** 382 * Returns the maximum number to pooled Connections that this factory will allow before it 383 * begins to return connections from the pool on calls to ({@link #createConnection}. 384 * 385 * @return the maxConnections that will be created for this pool. 386 */ 387 public int getMaxConnections() { 388 return getConnectionsPool().getMaxIdlePerKey(); 389 } 390 391 /** 392 * Sets the maximum number of pooled Connections (defaults to one). Each call to 393 * {@link #createConnection} will result in a new Connection being create up to the max 394 * connections value. 395 * 396 * @param maxConnections the maxConnections to set 397 */ 398 public void setMaxConnections(int maxConnections) { 399 getConnectionsPool().setMaxIdlePerKey(maxConnections); 400 getConnectionsPool().setMaxTotalPerKey(maxConnections); 401 } 402 403 /** 404 * Gets the Idle timeout value applied to new Connection's that are created by this pool. 405 * <p/> 406 * The idle timeout is used determine if a Connection instance has sat to long in the pool unused 407 * and if so is closed and removed from the pool. The default value is 30 seconds. 408 * 409 * @return idle timeout value (milliseconds) 410 */ 411 public int getIdleTimeout() { 412 return idleTimeout; 413 } 414 415 /** 416 * Sets the idle timeout value for Connection's that are created by this pool in Milliseconds, 417 * defaults to 30 seconds. 418 * <p/> 419 * For a Connection that is in the pool but has no current users the idle timeout determines how 420 * long the Connection can live before it is eligible for removal from the pool. Normally the 421 * connections are tested when an attempt to check one out occurs so a Connection instance can sit 422 * in the pool much longer than its idle timeout if connections are used infrequently. 423 * 424 * @param idleTimeout 425 * The maximum time a pooled Connection can sit unused before it is eligible for removal. 426 */ 427 public void setIdleTimeout(int idleTimeout) { 428 this.idleTimeout = idleTimeout; 429 } 430 431 /** 432 * allow connections to expire, irrespective of load or idle time. This is useful with failover 433 * to force a reconnect from the pool, to reestablish load balancing or use of the master post recovery 434 * 435 * @param expiryTimeout non zero in milliseconds 436 */ 437 public void setExpiryTimeout(long expiryTimeout) { 438 this.expiryTimeout = expiryTimeout; 439 } 440 441 /** 442 * @return the configured expiration timeout for connections in the pool. 443 */ 444 public long getExpiryTimeout() { 445 return expiryTimeout; 446 } 447 448 /** 449 * @return true if a Connection is created immediately on a call to {@link start}. 450 */ 451 public boolean isCreateConnectionOnStartup() { 452 return createConnectionOnStartup; 453 } 454 455 /** 456 * Whether to create a connection on starting this {@link PooledConnectionFactory}. 457 * <p/> 458 * This can be used to warm-up the pool on startup. Notice that any kind of exception 459 * happens during startup is logged at WARN level and ignored. 460 * 461 * @param createConnectionOnStartup <tt>true</tt> to create a connection on startup 462 */ 463 public void setCreateConnectionOnStartup(boolean createConnectionOnStartup) { 464 this.createConnectionOnStartup = createConnectionOnStartup; 465 } 466 467 /** 468 * Should Sessions use one anonymous producer for all producer requests or should a new 469 * MessageProducer be created for each request to create a producer object, default is true. 470 * 471 * When enabled the session only needs to allocate one MessageProducer for all requests and 472 * the MessageProducer#send(destination, message) method can be used. Normally this is the 473 * right thing to do however it does result in the Broker not showing the producers per 474 * destination. 475 * 476 * @return true if a PooledSession will use only a single anonymous message producer instance. 477 */ 478 public boolean isUseAnonymousProducers() { 479 return this.useAnonymousProducers; 480 } 481 482 /** 483 * Sets whether a PooledSession uses only one anonymous MessageProducer instance or creates 484 * a new MessageProducer for each call the create a MessageProducer. 485 * 486 * @param value 487 * Boolean value that configures whether anonymous producers are used. 488 */ 489 public void setUseAnonymousProducers(boolean value) { 490 this.useAnonymousProducers = value; 491 } 492 493 /** 494 * Gets the Pool of ConnectionPool instances which are keyed by different ConnectionKeys. 495 * 496 * @return this factories pool of ConnectionPool instances. 497 */ 498 protected GenericKeyedObjectPool<ConnectionKey, ConnectionPool> getConnectionsPool() { 499 initConnectionsPool(); 500 return this.connectionsPool; 501 } 502 503 /** 504 * Sets the number of milliseconds to sleep between runs of the idle Connection eviction thread. 505 * When non-positive, no idle object eviction thread will be run, and Connections will only be 506 * checked on borrow to determine if they have sat idle for too long or have failed for some 507 * other reason. 508 * <p/> 509 * By default this value is set to -1 and no expiration thread ever runs. 510 * 511 * @param timeBetweenExpirationCheckMillis 512 * The time to wait between runs of the idle Connection eviction thread. 513 */ 514 public void setTimeBetweenExpirationCheckMillis(long timeBetweenExpirationCheckMillis) { 515 getConnectionsPool().setTimeBetweenEvictionRunsMillis(timeBetweenExpirationCheckMillis); 516 } 517 518 /** 519 * @return the number of milliseconds to sleep between runs of the idle connection eviction thread. 520 */ 521 public long getTimeBetweenExpirationCheckMillis() { 522 return getConnectionsPool().getTimeBetweenEvictionRunsMillis(); 523 } 524 525 /** 526 * @return the number of Connections currently in the Pool 527 */ 528 public int getNumConnections() { 529 return getConnectionsPool().getNumIdle(); 530 } 531 532 /** 533 * Delegate that creates each instance of an ConnectionPool object. Subclasses can override 534 * this method to customize the type of connection pool returned. 535 * 536 * @param connection 537 * 538 * @return instance of a new ConnectionPool. 539 */ 540 protected ConnectionPool createConnectionPool(Connection connection) { 541 return new ConnectionPool(connection); 542 } 543 544 /** 545 * Returns the timeout to use for blocking creating new sessions 546 * 547 * @return true if the pooled Connection createSession method will block when the limit is hit. 548 * @see #setBlockIfSessionPoolIsFull(boolean) 549 */ 550 public long getBlockIfSessionPoolIsFullTimeout() { 551 return blockIfSessionPoolIsFullTimeout; 552 } 553 554 /** 555 * Controls the behavior of the internal session pool. By default the call to 556 * Connection.getSession() will block if the session pool is full. This setting 557 * will affect how long it blocks and throws an exception after the timeout. 558 * 559 * The size of the session pool is controlled by the @see #maximumActive 560 * property. 561 * 562 * Whether or not the call to create session blocks is controlled by the @see #blockIfSessionPoolIsFull 563 * property 564 * 565 * @param blockIfSessionPoolIsFullTimeout - if blockIfSessionPoolIsFullTimeout is true, 566 * then use this setting to configure how long to block before retry 567 */ 568 public void setBlockIfSessionPoolIsFullTimeout(long blockIfSessionPoolIsFullTimeout) { 569 this.blockIfSessionPoolIsFullTimeout = blockIfSessionPoolIsFullTimeout; 570 } 571 572 /** 573 * @return true if the underlying connection will be renewed on JMSException, false otherwise 574 */ 575 public boolean isReconnectOnException() { 576 return reconnectOnException; 577 } 578 579 /** 580 * Controls weather the underlying connection should be reset (and renewed) on JMSException 581 * 582 * @param reconnectOnException 583 * Boolean value that configures whether reconnect on exception should happen 584 */ 585 public void setReconnectOnException(boolean reconnectOnException) { 586 this.reconnectOnException = reconnectOnException; 587 } 588 589 /** 590 * Called by any superclass that implements a JNDIReferencable or similar that needs to collect 591 * the properties of this class for storage etc. 592 * 593 * This method should be updated any time there is a new property added. 594 * 595 * @param props 596 * a properties object that should be filled in with this objects property values. 597 */ 598 protected void populateProperties(Properties props) { 599 props.setProperty("maximumActiveSessionPerConnection", Integer.toString(getMaximumActiveSessionPerConnection())); 600 props.setProperty("maxConnections", Integer.toString(getMaxConnections())); 601 props.setProperty("idleTimeout", Integer.toString(getIdleTimeout())); 602 props.setProperty("expiryTimeout", Long.toString(getExpiryTimeout())); 603 props.setProperty("timeBetweenExpirationCheckMillis", Long.toString(getTimeBetweenExpirationCheckMillis())); 604 props.setProperty("createConnectionOnStartup", Boolean.toString(isCreateConnectionOnStartup())); 605 props.setProperty("useAnonymousProducers", Boolean.toString(isUseAnonymousProducers())); 606 props.setProperty("blockIfSessionPoolIsFullTimeout", Long.toString(getBlockIfSessionPoolIsFullTimeout())); 607 props.setProperty("reconnectOnException", Boolean.toString(isReconnectOnException())); 608 } 609}