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}