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