001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2003-2006 Robin Bagot and others
008// Copyright (C) 2003-2005 Julian Hyde
009// Copyright (C) 2005-2010 Pentaho
010// All Rights Reserved.
011*/
012package mondrian.rolap;
013
014import mondrian.olap.Util;
015
016import org.apache.commons.dbcp.*;
017import org.apache.commons.pool.ObjectPool;
018import org.apache.commons.pool.impl.GenericObjectPool;
019
020import java.util.*;
021import javax.sql.DataSource;
022
023/**
024 * Singleton class that holds a connection pool.
025 * Call RolapConnectionPool.instance().getPoolingDataSource(connectionFactory)
026 * to get a DataSource in return that is a pooled data source.
027 *
028 * @author jhyde
029 * @author Robin Bagot
030 * @since 7 July, 2003
031 */
032class RolapConnectionPool {
033
034    public static RolapConnectionPool instance() {
035        return instance;
036    }
037
038    private static final RolapConnectionPool instance =
039        new RolapConnectionPool();
040
041    private final Map<Object, ObjectPool> mapConnectKeyToPool =
042        new HashMap<Object, ObjectPool>();
043
044    private final Map<Object, DataSource> dataSourceMap =
045        new WeakHashMap<Object, DataSource>();
046
047    private RolapConnectionPool() {
048    }
049
050
051    /**
052     * Sets up a pooling data source for connection pooling.
053     * This can be used if the application server does not have a pooling
054     * dataSource already configured.
055     *
056     * <p>This takes a normal jdbc connection string, and requires a jdbc
057     * driver to be loaded, and then uses a
058     * {@link DriverManagerConnectionFactory} to create connections to the
059     * database.
060     *
061     * <p>An alternative method of configuring a pooling driver is to use an
062     * external configuration file. See the the Apache jakarta-commons
063     * commons-pool documentation.
064     *
065     * @param key Identifies which connection factory to use. A typical key is
066     *   a JDBC connect string, since each JDBC connect string requires a
067     *   different connection factory.
068     * @param connectionFactory Creates connections from an underlying
069     *   JDBC connect string or DataSource
070     * @return a pooling DataSource object
071     */
072    public synchronized DataSource getPoolingDataSource(
073        Object key,
074        ConnectionFactory connectionFactory)
075    {
076        ObjectPool connectionPool = getPool(key, connectionFactory);
077        // create pooling datasource
078        return new PoolingDataSource(connectionPool);
079    }
080
081    /**
082     * Clears the connection pool for testing purposes
083     */
084    void clearPool() {
085        mapConnectKeyToPool.clear();
086    }
087
088    public synchronized DataSource getDriverManagerPoolingDataSource(
089        String jdbcConnectString,
090        Properties jdbcProperties)
091    {
092        // First look for a data source with identical specification. This in
093        // turn helps us to use the cache of Dialect objects.
094
095        // Need to include user name to define the pool key as some DBMSs
096        // like Oracle don't include schemas in the JDBC URL - instead the
097        // user drives the schema. This makes JDBC pools act like JNDI pools,
098        // with, in effect, a pool per DB user.
099
100        List<Object> key =
101            Arrays.<Object>asList(
102                "DriverManagerPoolingDataSource",
103                jdbcConnectString,
104                jdbcProperties);
105        DataSource dataSource = dataSourceMap.get(key);
106        if (dataSource != null) {
107            return dataSource;
108        }
109
110        // use the DriverManagerConnectionFactory to create connections
111        ConnectionFactory connectionFactory =
112            new DriverManagerConnectionFactory(
113                jdbcConnectString,
114                jdbcProperties);
115
116        try {
117            String propertyString = jdbcProperties.toString();
118            dataSource = getPoolingDataSource(
119                jdbcConnectString + propertyString,
120                connectionFactory);
121        } catch (Throwable e) {
122            throw Util.newInternal(
123                e,
124                "Error while creating connection pool (with URI "
125                + jdbcConnectString + ")");
126        }
127        dataSourceMap.put(key, dataSource);
128        return dataSource;
129    }
130
131    public synchronized DataSource getDataSourcePoolingDataSource(
132        DataSource dataSource,
133        String dataSourceName,
134        String jdbcUser,
135        String jdbcPassword)
136    {
137        // First look for a data source with identical specification. This in
138        // turn helps us to use the cache of Dialect objects.
139        List<Object> key =
140            Arrays.asList(
141                "DataSourcePoolingDataSource",
142                dataSource,
143                jdbcUser,
144                jdbcPassword);
145        DataSource pooledDataSource = dataSourceMap.get(key);
146        if (pooledDataSource != null) {
147            return pooledDataSource;
148        }
149
150        ConnectionFactory connectionFactory;
151        if (jdbcUser != null || jdbcPassword != null) {
152            connectionFactory =
153                new DataSourceConnectionFactory(
154                    dataSource, jdbcUser, jdbcPassword);
155        } else {
156            connectionFactory =
157                new DataSourceConnectionFactory(dataSource);
158        }
159        try {
160            pooledDataSource =
161                getPoolingDataSource(
162                    dataSourceName,
163                    connectionFactory);
164        } catch (Exception e) {
165            throw Util.newInternal(
166                e,
167                "Error while creating connection pool (with URI "
168                + dataSourceName + ")");
169        }
170        dataSourceMap.put(key, pooledDataSource);
171        return dataSource;
172    }
173
174    /**
175     * Gets or creates a connection pool for a particular connect
176     * specification.
177     */
178    private synchronized ObjectPool getPool(
179        Object key,
180        ConnectionFactory connectionFactory)
181    {
182        ObjectPool connectionPool = mapConnectKeyToPool.get(key);
183        if (connectionPool == null) {
184            // use GenericObjectPool, which provides for resource limits
185            connectionPool = new GenericObjectPool(
186                null, // PoolableObjectFactory, can be null
187                50, // max active
188                GenericObjectPool.WHEN_EXHAUSTED_BLOCK, // action when exhausted
189                3000, // max wait (milli seconds)
190                10, // max idle
191                false, // test on borrow
192                false, // test on return
193                60000, // time between eviction runs (millis)
194                5, // number to test on eviction run
195                30000, // min evictable idle time (millis)
196                true); // test while idle
197
198            // create a PoolableConnectionFactory
199            AbandonedConfig abandonedConfig = new AbandonedConfig();
200            // flag to remove abandoned connections from pool
201            abandonedConfig.setRemoveAbandoned(true);
202            // timeout (seconds) before removing abandoned connections
203            abandonedConfig.setRemoveAbandonedTimeout(300);
204            // Flag to log stack traces for application code which abandoned a
205            // Statement or Connection
206            abandonedConfig.setLogAbandoned(true);
207            PoolableConnectionFactory poolableConnectionFactory =
208                new PoolableConnectionFactory(
209                    // the connection factory
210                    connectionFactory,
211                    // the object pool
212                    connectionPool,
213                    // statement pool factory for pooling prepared statements,
214                    // or null for no pooling
215                    null,
216                    // validation query (must return at least 1 row e.g. Oracle:
217                    // select count(*) from dual) to test connection, can be
218                    // null
219                    null,
220                    // default "read only" setting for borrowed connections
221                    false,
222                    // default "auto commit" setting for returned connections
223                    true,
224                    // AbandonedConfig object configures how to handle abandoned
225                    // connections
226                    abandonedConfig);
227
228            // "poolableConnectionFactory" has registered itself with
229            // "connectionPool", somehow, so we don't need the value any more.
230            Util.discard(poolableConnectionFactory);
231            mapConnectKeyToPool.put(key, connectionPool);
232        }
233        return connectionPool;
234    }
235
236}
237
238// End RolapConnectionPool.java