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