001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (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 & Co. KG, 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.sql.SQLException; 038import java.util.Map; 039import java.util.Properties; 040 041import com.google.common.collect.Maps; 042import com.zaxxer.hikari.HikariConfig; 043import com.zaxxer.hikari.HikariDataSource; 044 045/** 046 * Database connection pool class using HikariCP. 047 */ 048public final class CmsDbPoolV11 { 049 050 /** Prefix for database keys. */ 051 public static final String KEY_DATABASE = "db."; 052 053 /** Key for the database name. */ 054 public static final String KEY_DATABASE_NAME = KEY_DATABASE + "name"; 055 056 /** Key for the pool id. */ 057 public static final String KEY_DATABASE_POOL = KEY_DATABASE + "pool"; 058 059 /** Key for number of connection attempts. */ 060 public static final String KEY_CONNECT_ATTEMTS = "connects"; 061 062 /** Key for connection waiting. */ 063 public static final String KEY_CONNECT_WAITS = "wait"; 064 065 /** Key for the entity manager pool size. */ 066 public static final String KEY_ENTITY_MANAGER_POOL_SIZE = "entityMangerPoolSize"; 067 068 /** Key for jdbc driver. */ 069 public static final String KEY_JDBC_DRIVER = "jdbcDriver"; 070 071 /** Key for jdbc url. */ 072 public static final String KEY_JDBC_URL = "jdbcUrl"; 073 074 /** Key for jdbc url params. */ 075 public static final String KEY_JDBC_URL_PARAMS = KEY_JDBC_URL + ".params"; 076 077 /** Key for database password. */ 078 public static final String KEY_PASSWORD = "password"; 079 080 /** Key for default. */ 081 public static final String KEY_POOL_DEFAULT = "default"; 082 083 /** Key for pool url. */ 084 public static final String KEY_POOL_URL = "poolUrl"; 085 086 /** Key for pool user. */ 087 public static final String KEY_POOL_USER = "user"; 088 089 /** Key for vfs pool. */ 090 public static final String KEY_POOL_VFS = "vfs"; 091 092 /** Key for user name. */ 093 public static final String KEY_USERNAME = "user"; 094 095 /** The name of the opencms default pool. */ 096 public static final String OPENCMS_DEFAULT_POOL_NAME = "default"; 097 098 /** The default OpenCms JDBC pool URL. */ 099 public static final String OPENCMS_DEFAULT_POOL_URL = "opencms:default"; 100 101 /** The prefix used for opencms JDBC pools. */ 102 public static final String OPENCMS_URL_PREFIX = "opencms:"; 103 104 /** Map of default test queries. */ 105 private static Map<String, String> testQueries = Maps.newHashMap(); 106 107 static { 108 testQueries.put("com.ibm.db2.jcc.DB2Driver", "SELECT 1 FROM SYSIBM.SYSDUMMY1"); 109 testQueries.put("net.sourceforge.jtds.jdbc.Driver", "SELECT 1"); 110 testQueries.put("oracle.jdbc.driver.OracleDriver", "SELECT 1 FROM DUAL"); 111 testQueries.put("com.ibm.as400.access.AS400JDBCDriver", "SELECT NOW()"); 112 } 113 114 /** The opencms pool url. */ 115 private String m_poolUrl; 116 117 /** The HikariCP data source. */ 118 private HikariDataSource m_dataSource; 119 120 /** 121 * Default constructor.<p> 122 * 123 * @param config the OpenCms configuration (opencms.properties) 124 * @param key the name of the pool (without the opencms: prefix) 125 * 126 * @throws Exception if something goes wrong 127 */ 128 public CmsDbPoolV11(CmsParameterConfiguration config, String key) 129 throws Exception { 130 HikariConfig hikariConf = createHikariConfig(config, key); 131 m_poolUrl = OPENCMS_URL_PREFIX + key; 132 m_dataSource = new HikariDataSource(hikariConf); 133 Connection con = null; 134 boolean connect = false; 135 int connectionTests = 0; 136 int connectionAttempts = config.getInteger(KEY_DATABASE_POOL + '.' + key + '.' + KEY_CONNECT_ATTEMTS, 10); 137 int connectionsWait = config.getInteger(KEY_DATABASE_POOL + '.' + key + '.' + KEY_CONNECT_WAITS, 5000); 138 139 // try to connect once to the database to ensure it can be connected to at all 140 // if the conection cannot be established, multiple attempts will be done to connect 141 // just in cast the database was not fast enough to start before OpenCms was started 142 143 do { 144 try { 145 // try to connect 146 con = m_dataSource.getConnection(); 147 connect = true; 148 } catch (Exception e) { 149 // connection failed, increase attempts, sleept for some seconds and log a message 150 connectionTests++; 151 if (CmsLog.INIT.isInfoEnabled()) { 152 CmsLog.INIT.info( 153 Messages.get().getBundle().key( 154 Messages.INIT_WAIT_FOR_DB_4, 155 new Object[] { 156 getPoolUrl(), 157 m_dataSource.getJdbcUrl(), 158 new Integer(connectionTests), 159 new Integer(connectionsWait)})); 160 } 161 Thread.sleep(connectionsWait); 162 } finally { 163 if (con != null) { 164 con.close(); 165 } 166 } 167 } while (!connect && (connectionTests < connectionAttempts)); 168 169 if (CmsLog.INIT.isInfoEnabled()) { 170 CmsLog.INIT.info( 171 Messages.get().getBundle().key(Messages.INIT_JDBC_POOL_2, getPoolUrl(), m_dataSource.getJdbcUrl())); 172 } 173 } 174 175 /** 176 * Creates the HikariCP configuration based on the configuration of a pool defined in opencms.properties. 177 * 178 * @param config the configuration object with the properties 179 * @param key the pool name (without the opencms prefix) 180 * 181 * @return the HikariCP configuration for the pool 182 */ 183 public static HikariConfig createHikariConfig(CmsParameterConfiguration config, String key) { 184 185 Map<String, String> poolMap = Maps.newHashMap(); 186 for (Map.Entry<String, String> entry : config.entrySet()) { 187 String suffix = getPropertyRelativeSuffix(KEY_DATABASE_POOL + "." + key, entry.getKey()); 188 if ((suffix != null) && !CmsStringUtil.isEmptyOrWhitespaceOnly(entry.getValue())) { 189 String value = entry.getValue().trim(); 190 poolMap.put(suffix, value); 191 } 192 } 193 194 // these are for backwards compatibility , all other properties not of the form db.pool.poolname.v11..... are ignored 195 String jdbcUrl = poolMap.get(KEY_JDBC_URL); 196 String params = poolMap.get(KEY_JDBC_URL_PARAMS); 197 String driver = poolMap.get(KEY_JDBC_DRIVER); 198 String user = poolMap.get(KEY_USERNAME); 199 String password = poolMap.get(KEY_PASSWORD); 200 String poolName = OPENCMS_URL_PREFIX + key; 201 202 if ((params != null) && (jdbcUrl != null)) { 203 jdbcUrl += params; 204 } 205 206 Properties hikariProps = new Properties(); 207 208 if (jdbcUrl != null) { 209 hikariProps.put("jdbcUrl", jdbcUrl); 210 } 211 if (driver != null) { 212 hikariProps.put("driverClassName", driver); 213 } 214 if (user != null) { 215 user = OpenCms.getCredentialsResolver().resolveCredential(I_CmsCredentialsResolver.DB_USER, user); 216 hikariProps.put("username", user); 217 } 218 if (password != null) { 219 password = OpenCms.getCredentialsResolver().resolveCredential( 220 I_CmsCredentialsResolver.DB_PASSWORD, 221 password); 222 hikariProps.put("password", password); 223 } 224 225 hikariProps.put("maximumPoolSize", "30"); 226 227 // Properties of the form db.pool.poolname.v11.<foo> are directly passed to HikariCP as <foo> 228 for (Map.Entry<String, String> entry : poolMap.entrySet()) { 229 String suffix = getPropertyRelativeSuffix("v11", entry.getKey()); 230 if (suffix != null) { 231 hikariProps.put(suffix, entry.getValue()); 232 } 233 } 234 235 String configuredTestQuery = (String)(hikariProps.get("connectionTestQuery")); 236 String testQueryForDriver = testQueries.get(driver); 237 if ((testQueryForDriver != null) && CmsStringUtil.isEmptyOrWhitespaceOnly(configuredTestQuery)) { 238 hikariProps.put("connectionTestQuery", testQueryForDriver); 239 } 240 hikariProps.put("registerMbeans", "true"); 241 HikariConfig result = new HikariConfig(hikariProps); 242 243 result.setPoolName(poolName.replace(":", "_")); 244 return result; 245 } 246 247 /** 248 * Returns the name of the default database connection pool.<p> 249 * 250 * @return the name of the default database connection pool 251 */ 252 public static String getDefaultDbPoolName() { 253 254 return OPENCMS_DEFAULT_POOL_NAME; 255 } 256 257 /** 258 * If str starts with prefix + '.', return the remaining part, otherwise return null.<p> 259 * 260 * @param prefix the prefix 261 * @param str the string to remove the prefix from 262 * @return str with the prefix removed, or null if it didn't start with prefix + '.' 263 */ 264 public static String getPropertyRelativeSuffix(String prefix, String str) { 265 266 String realPrefix = prefix + "."; 267 if (str.startsWith(realPrefix)) { 268 return str.substring(realPrefix.length()); 269 } 270 return null; 271 } 272 273 /** 274 * Closes the pool.<p> 275 * 276 * @throws Exception if something goes wrong 277 */ 278 public void close() throws Exception { 279 280 m_dataSource.close(); 281 } 282 283 /** 284 * Returns the number of active connections.<p> 285 * 286 * @return the number of active connections 287 */ 288 public int getActiveConnections() { 289 290 return m_dataSource.getHikariPoolMXBean().getActiveConnections(); 291 } 292 293 /** 294 * Gets a database connection from the pool.<p> 295 * 296 * @return the database connection 297 * @throws SQLException if something goes wrong 298 */ 299 public Connection getConnection() throws SQLException { 300 301 return m_dataSource.getConnection(); 302 } 303 304 /** 305 * Gets the number of idle connections.<p> 306 * 307 * @return the number of idle connections 308 */ 309 public int getIdleConnections() { 310 311 return m_dataSource.getHikariPoolMXBean().getIdleConnections(); 312 } 313 314 /** 315 * Gets the pool url.<p> 316 * 317 * @return the pool url 318 */ 319 public String getPoolUrl() { 320 321 return m_poolUrl; 322 } 323}