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