001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.db; 029 030import org.opencms.configuration.CmsParameterConfiguration; 031import org.opencms.main.CmsLog; 032import org.opencms.main.OpenCms; 033import org.opencms.security.I_CmsCredentialsResolver; 034import org.opencms.util.CmsStringUtil; 035 036import java.sql.Connection; 037import java.util.ArrayList; 038import java.util.List; 039import java.util.Properties; 040 041import org.apache.commons.dbcp.ConnectionFactory; 042import org.apache.commons.dbcp.DriverManagerConnectionFactory; 043import org.apache.commons.dbcp.PoolableConnectionFactory; 044import org.apache.commons.dbcp.PoolingDriver; 045import org.apache.commons.pool.impl.GenericKeyedObjectPool; 046import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory; 047import org.apache.commons.pool.impl.GenericObjectPool; 048 049/** 050 * Various methods to create DBCP pools.<p> 051 * 052 * Only JDBC Driver based pools are supported currently. JNDI DataSource 053 * based pools might be added probably later.<p> 054 * 055 * <b>Please note:</b> This class is subject to change in later versions. 056 * To obtain information about the connections, please use the 057 * {@link org.opencms.db.CmsSqlManager}.<p> 058 * 059 * @since 6.0.0 060 */ 061public final class CmsDbPool { 062 063 /** This prefix is required to make the JDBC DriverManager return pooled DBCP connections. */ 064 public static final String DBCP_JDBC_URL_PREFIX = "jdbc:apache:commons:dbcp:"; 065 066 /** Key for number of connection attempts. */ 067 public static final String KEY_CONNECT_ATTEMTS = "connects"; 068 069 /** Key for connection waiting. */ 070 public static final String KEY_CONNECT_WAITS = "wait"; 071 072 /** Prefix for database keys. */ 073 public static final String KEY_DATABASE = "db."; 074 075 /** Key for the database name. */ 076 public static final String KEY_DATABASE_NAME = KEY_DATABASE + "name"; 077 078 /** Key for the pool id. */ 079 public static final String KEY_DATABASE_POOL = KEY_DATABASE + "pool"; 080 081 /** Key for statement pooling. */ 082 public static final String KEY_DATABASE_STATEMENTS = KEY_DATABASE + "statements"; 083 084 /** Key for the entity manager pool size. */ 085 public static final String KEY_ENTITY_MANAGER_POOL_SIZE = "entityMangerPoolSize"; 086 087 /** 088 * Key for the connection properties (optional). Alternative to {@link #KEY_JDBC_URL_PARAMS jdbc url params} to 089 * configure the jdbc driver. (Notice that not all drivers support both configuration methods.) 090 * <p> 091 * From the Apache's Commons DBCP 1.4 API: 'list of arbitrary string tag/value pairs as connection arguments; 092 * normally at least a "user" and "password" property should be included.' 093 */ 094 public static final String KEY_CONNECTION_PROPERTIES = "connectionProperties"; 095 096 /** Key for jdbc driver. */ 097 public static final String KEY_JDBC_DRIVER = "jdbcDriver"; 098 099 /** Key for jdbc url. */ 100 public static final String KEY_JDBC_URL = "jdbcUrl"; 101 102 /** Key for jdbc url params. */ 103 public static final String KEY_JDBC_URL_PARAMS = KEY_JDBC_URL + ".params"; 104 105 /** Key for maximum active connections. */ 106 public static final String KEY_MAX_ACTIVE = "maxActive"; 107 108 /** Key for maximum idle connections. */ 109 public static final String KEY_MAX_IDLE = "maxIdle"; 110 111 /** Key for maximum wait time. */ 112 public static final String KEY_MAX_WAIT = "maxWait"; 113 114 /** Key for minimum idle time before a connection is subject to an eviction test. */ 115 public static final String KEY_MIN_EVICTABLE_IDLE_TIME = "minEvictableIdleTime"; 116 117 /** Key for minimum number of connections kept open. */ 118 public static final String KEY_MIN_IDLE = "minIdle"; 119 120 /** Key for number of tested connections per run. */ 121 public static final String KEY_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun"; 122 123 /** Key for database password. */ 124 public static final String KEY_PASSWORD = "password"; 125 126 /** Key for default. */ 127 public static final String KEY_POOL_DEFAULT = "default"; 128 129 /** Key for pool url. */ 130 public static final String KEY_POOL_URL = "poolUrl"; 131 132 /** Key for pool user. */ 133 public static final String KEY_POOL_USER = "user"; 134 135 /** Key for vfs pool. */ 136 public static final String KEY_POOL_VFS = "vfs"; 137 138 /** Key for pooling flag. */ 139 public static final String KEY_POOLING = "pooling"; 140 141 /** Key for test on borrow flag. */ 142 public static final String KEY_TEST_ON_BORROW = "testOnBorrow"; 143 144 /** Key for test query. */ 145 public static final String KEY_TEST_QUERY = "testQuery"; 146 147 /** Key for test while idle flag. */ 148 public static final String KEY_TEST_WHILE_IDLE = "testWhileIdle"; 149 150 /** Key for time between two eviction runs. */ 151 public static final String KEY_TIME_BETWEEN_EVICTION_RUNS = "timeBetweenEvictionRuns"; 152 153 /** Key for user name. */ 154 public static final String KEY_USERNAME = "user"; 155 156 /** Key for "when pool exhausted" action. */ 157 public static final String KEY_WHEN_EXHAUSTED_ACTION = "whenExhaustedAction"; 158 159 /** The name of the opencms default pool. */ 160 public static final String OPENCMS_DEFAULT_POOL_NAME = "default"; 161 162 /** The default OpenCms JDBC pool URL. */ 163 public static final String OPENCMS_DEFAULT_POOL_URL = "opencms:default"; 164 165 /** The prefix used for opencms JDBC pools. */ 166 public static final String OPENCMS_URL_PREFIX = "opencms:"; 167 168 /** 169 * Default constructor.<p> 170 * 171 * Nobody is allowed to create an instance of this class! 172 */ 173 private CmsDbPool() { 174 175 super(); 176 } 177 178 /** 179 * Creates a JDBC DriverManager based DBCP connection pool.<p> 180 * 181 * @param config the configuration (opencms.properties) 182 * @param key the key of the database pool in the configuration 183 * @return String the URL to access the created DBCP pool 184 * @throws Exception if the pool could not be initialized 185 */ 186 public static PoolingDriver createDriverManagerConnectionPool(CmsParameterConfiguration config, String key) 187 throws Exception { 188 189 // read the values of the pool configuration specified by the given key 190 String jdbcDriver = config.get(KEY_DATABASE_POOL + '.' + key + '.' + KEY_JDBC_DRIVER); 191 String jdbcUrl = config.get(KEY_DATABASE_POOL + '.' + key + '.' + KEY_JDBC_URL); 192 String jdbcUrlParams = config.get(KEY_DATABASE_POOL + '.' + key + '.' + KEY_JDBC_URL_PARAMS); 193 int maxActive = config.getInteger(KEY_DATABASE_POOL + '.' + key + '.' + KEY_MAX_ACTIVE, 10); 194 int maxWait = config.getInteger(KEY_DATABASE_POOL + '.' + key + '.' + KEY_MAX_WAIT, 2000); 195 int maxIdle = config.getInteger(KEY_DATABASE_POOL + '.' + key + '.' + KEY_MAX_IDLE, 5); 196 int minEvictableIdleTime = config.getInteger( 197 KEY_DATABASE_POOL + '.' + key + '.' + KEY_MIN_EVICTABLE_IDLE_TIME, 198 1800000); 199 int minIdle = config.getInteger(KEY_DATABASE_POOL + '.' + key + '.' + KEY_MIN_IDLE, 0); 200 int numTestsPerEvictionRun = config.getInteger( 201 KEY_DATABASE_POOL + '.' + key + '.' + KEY_NUM_TESTS_PER_EVICTION_RUN, 202 3); 203 int timeBetweenEvictionRuns = config.getInteger( 204 KEY_DATABASE_POOL + '.' + key + '.' + KEY_TIME_BETWEEN_EVICTION_RUNS, 205 3600000); 206 String testQuery = config.get(KEY_DATABASE_POOL + '.' + key + '.' + KEY_TEST_QUERY); 207 String username = config.get(KEY_DATABASE_POOL + '.' + key + '.' + KEY_USERNAME); 208 username = OpenCms.getCredentialsResolver().resolveCredential(I_CmsCredentialsResolver.DB_USER, username); 209 String password = config.get(KEY_DATABASE_POOL + '.' + key + '.' + KEY_PASSWORD); 210 password = OpenCms.getCredentialsResolver().resolveCredential(I_CmsCredentialsResolver.DB_PASSWORD, password); 211 String poolUrl = config.get(KEY_DATABASE_POOL + '.' + key + '.' + KEY_POOL_URL); 212 String whenExhaustedActionValue = config.get( 213 KEY_DATABASE_POOL + '.' + key + '.' + KEY_WHEN_EXHAUSTED_ACTION).trim(); 214 byte whenExhaustedAction = 0; 215 boolean testOnBorrow = Boolean.valueOf( 216 config.getString(KEY_DATABASE_POOL + '.' + key + '.' + KEY_TEST_ON_BORROW, "false").trim()).booleanValue(); 217 boolean testWhileIdle = Boolean.valueOf( 218 config.getString(KEY_DATABASE_POOL + '.' + key + '.' + KEY_TEST_WHILE_IDLE, "false").trim()).booleanValue(); 219 220 if ("block".equalsIgnoreCase(whenExhaustedActionValue)) { 221 whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_BLOCK; 222 } else if ("fail".equalsIgnoreCase(whenExhaustedActionValue)) { 223 whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_FAIL; 224 } else if ("grow".equalsIgnoreCase(whenExhaustedActionValue)) { 225 whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_GROW; 226 } else { 227 whenExhaustedAction = GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION; 228 } 229 230 if ("".equals(testQuery)) { 231 testQuery = null; 232 } 233 234 if (username == null) { 235 username = ""; 236 } 237 238 if (password == null) { 239 password = ""; 240 } 241 242 // read the values of the statement pool configuration specified by the given key 243 boolean poolingStmts = Boolean.valueOf( 244 config.getString( 245 KEY_DATABASE_STATEMENTS + '.' + key + '.' + KEY_POOLING, 246 CmsStringUtil.TRUE).trim()).booleanValue(); 247 int maxActiveStmts = config.getInteger(KEY_DATABASE_STATEMENTS + '.' + key + '.' + KEY_MAX_ACTIVE, 25); 248 int maxWaitStmts = config.getInteger(KEY_DATABASE_STATEMENTS + '.' + key + '.' + KEY_MAX_WAIT, 250); 249 int maxIdleStmts = config.getInteger(KEY_DATABASE_STATEMENTS + '.' + key + '.' + KEY_MAX_IDLE, 15); 250 String whenStmtsExhaustedActionValue = config.get( 251 KEY_DATABASE_STATEMENTS + '.' + key + '.' + KEY_WHEN_EXHAUSTED_ACTION); 252 byte whenStmtsExhaustedAction = GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW; 253 if (whenStmtsExhaustedActionValue != null) { 254 whenStmtsExhaustedActionValue = whenStmtsExhaustedActionValue.trim(); 255 whenStmtsExhaustedAction = ("block".equalsIgnoreCase(whenStmtsExhaustedActionValue)) 256 ? GenericKeyedObjectPool.WHEN_EXHAUSTED_BLOCK 257 : ("fail".equalsIgnoreCase(whenStmtsExhaustedActionValue)) 258 ? GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL 259 : GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW; 260 } 261 262 int connectionAttempts = config.getInteger(KEY_DATABASE_POOL + '.' + key + '.' + KEY_CONNECT_ATTEMTS, 10); 263 int connetionsWait = config.getInteger(KEY_DATABASE_POOL + '.' + key + '.' + KEY_CONNECT_WAITS, 5000); 264 265 // create an instance of the JDBC driver 266 Class.forName(jdbcDriver).newInstance(); 267 268 // initialize a keyed object pool to store connections 269 GenericObjectPool connectionPool = new GenericObjectPool(null); 270 271 /* Abandoned pool configuration: 272 * 273 * In case the systems encounters "pool exhaustion" (runs out of connections), 274 * comment the above line with "new GenericObjectPool(null)" and uncomment the 275 * 5 lines below. This will generate an "abandoned pool" configuration that logs 276 * abandoned connections to the System.out. Unfortunatly this code is deprecated, 277 * so to avoid code warnings it's also disabled here. 278 * Tested with commons-pool v 1.2. 279 */ 280 281 // AbandonedConfig abandonedConfig = new AbandonedConfig(); 282 // abandonedConfig.setLogAbandoned(true); 283 // abandonedConfig.setRemoveAbandoned(true); 284 // abandonedConfig.setRemoveAbandonedTimeout(5); 285 // GenericObjectPool connectionPool = new AbandonedObjectPool(null, abandonedConfig); 286 // initialize an object pool to store connections 287 connectionPool.setMaxActive(maxActive); 288 connectionPool.setMaxIdle(maxIdle); 289 connectionPool.setMinIdle(minIdle); 290 connectionPool.setMaxWait(maxWait); 291 connectionPool.setWhenExhaustedAction(whenExhaustedAction); 292 293 if (testQuery != null) { 294 connectionPool.setTestOnBorrow(testOnBorrow); 295 connectionPool.setTestWhileIdle(testWhileIdle); 296 connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRuns); 297 connectionPool.setNumTestsPerEvictionRun(numTestsPerEvictionRun); 298 connectionPool.setMinEvictableIdleTimeMillis(minEvictableIdleTime); 299 } 300 301 // initialize a connection factory to make the DriverManager taking connections from the pool 302 if (jdbcUrlParams != null) { 303 jdbcUrl += jdbcUrlParams; 304 } 305 306 Properties connectionProperties = config.getPrefixedProperties( 307 KEY_DATABASE_POOL + '.' + key + '.' + KEY_CONNECTION_PROPERTIES); 308 connectionProperties.put(KEY_USERNAME, username); 309 connectionProperties.put(KEY_PASSWORD, password); 310 ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(jdbcUrl, connectionProperties); 311 312 // Set up statement pool, if desired 313 GenericKeyedObjectPoolFactory statementFactory = null; 314 if (poolingStmts) { 315 statementFactory = new GenericKeyedObjectPoolFactory( 316 null, 317 maxActiveStmts, 318 whenStmtsExhaustedAction, 319 maxWaitStmts, 320 maxIdleStmts); 321 } 322 323 // initialize a factory to obtain pooled connections and prepared statements 324 new PoolableConnectionFactory(connectionFactory, connectionPool, statementFactory, testQuery, false, true); 325 326 // initialize a new pooling driver using the pool 327 PoolingDriver driver = new PoolingDriver(); 328 driver.registerPool(poolUrl, connectionPool); 329 330 Connection con = null; 331 boolean connect = false; 332 int connectionTests = 0; 333 334 // try to connect once to the database to ensure it can be connected to at all 335 // if the conection cannot be established, multiple attempts will be done to connect 336 // just in cast the database was not fast enough to start before OpenCms was started 337 338 do { 339 try { 340 // try to connect 341 con = connectionFactory.createConnection(); 342 connect = true; 343 } catch (Exception e) { 344 // connection failed, increase attempts, sleept for some seconds and log a message 345 connectionTests++; 346 if (CmsLog.INIT.isInfoEnabled()) { 347 CmsLog.INIT.info( 348 Messages.get().getBundle().key( 349 Messages.INIT_WAIT_FOR_DB_4, 350 new Object[] { 351 poolUrl, 352 jdbcUrl, 353 new Integer(connectionTests), 354 new Integer(connetionsWait)})); 355 } 356 Thread.sleep(connetionsWait); 357 } finally { 358 if (con != null) { 359 con.close(); 360 } 361 } 362 } while (!connect && (connectionTests < connectionAttempts)); 363 364 if (CmsLog.INIT.isInfoEnabled()) { 365 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_JDBC_POOL_2, poolUrl, jdbcUrl)); 366 } 367 return driver; 368 } 369 370 /** 371 * Returns the database pool name for a given configuration key.<p> 372 * 373 * @param configuration the configuration 374 * @param key a db pool configuration key 375 * @return the database pool name 376 */ 377 public static String getDbPoolName(CmsParameterConfiguration configuration, String key) { 378 379 return configuration.get(KEY_DATABASE_POOL + '.' + key + '.' + KEY_POOL_URL); 380 } 381 382 /** 383 * Returns a list of available database pool names.<p> 384 * 385 * @param configuration the configuration to read the pool names from 386 * 387 * @return a list of database pool names 388 */ 389 public static List<String> getDbPoolUrls(CmsParameterConfiguration configuration) { 390 391 List<String> dbPoolNames = new ArrayList<String>(); 392 List<String> driverPoolNames = configuration.getList(CmsDriverManager.CONFIGURATION_DB + ".pools"); 393 394 for (String driverPoolName : driverPoolNames) { 395 dbPoolNames.add(getDbPoolName(configuration, driverPoolName)); 396 } 397 return dbPoolNames; 398 } 399 400 /** 401 * Returns the name of the default database connection pool.<p> 402 * 403 * @return the name of the default database connection pool 404 */ 405 public static String getDefaultDbPoolName() { 406 407 return OPENCMS_DEFAULT_POOL_NAME; 408 } 409}