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}