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.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 if (user != null) { 223 user = OpenCms.getCredentialsResolver().resolveCredential(I_CmsCredentialsResolver.DB_USER, user); 224 hikariProps.put("username", user); 225 } 226 if (password != null) { 227 password = OpenCms.getCredentialsResolver().resolveCredential( 228 I_CmsCredentialsResolver.DB_PASSWORD, 229 password); 230 hikariProps.put("password", password); 231 } 232 233 hikariProps.put("maximumPoolSize", "30"); 234 235 // Properties of the form db.pool.poolname.v11.<foo> are directly passed to HikariCP as <foo> 236 for (Map.Entry<String, String> entry : poolMap.entrySet()) { 237 String suffix = getPropertyRelativeSuffix("v11", entry.getKey()); 238 if (suffix != null) { 239 hikariProps.put(suffix, entry.getValue()); 240 } 241 } 242 243 String configuredTestQuery = (String)(hikariProps.get("connectionTestQuery")); 244 String testQueryForDriver = testQueries.get(driver); 245 if ((testQueryForDriver != null) && CmsStringUtil.isEmptyOrWhitespaceOnly(configuredTestQuery)) { 246 hikariProps.put("connectionTestQuery", testQueryForDriver); 247 } 248 hikariProps.put("registerMbeans", "true"); 249 HikariConfig result = new HikariConfig(hikariProps); 250 251 result.setPoolName(poolName.replace(":", "_")); 252 return result; 253 } 254 255 /** 256 * Returns the name of the default database connection pool.<p> 257 * 258 * @return the name of the default database connection pool 259 */ 260 public static String getDefaultDbPoolName() { 261 262 return OPENCMS_DEFAULT_POOL_NAME; 263 } 264 265 /** 266 * If str starts with prefix + '.', return the remaining part, otherwise return null.<p> 267 * 268 * @param prefix the prefix 269 * @param str the string to remove the prefix from 270 * @return str with the prefix removed, or null if it didn't start with prefix + '.' 271 */ 272 public static String getPropertyRelativeSuffix(String prefix, String str) { 273 274 String realPrefix = prefix + "."; 275 if (str.startsWith(realPrefix)) { 276 return str.substring(realPrefix.length()); 277 } 278 return null; 279 } 280 281 /** 282 * Closes the pool.<p> 283 * 284 * @throws Exception if something goes wrong 285 */ 286 public void close() throws Exception { 287 288 m_dataSource.close(); 289 } 290 291 /** 292 * Returns the number of active connections.<p> 293 * 294 * @return the number of active connections 295 */ 296 public int getActiveConnections() { 297 298 return m_dataSource.getHikariPoolMXBean().getActiveConnections(); 299 } 300 301 /** 302 * Gets a database connection from the pool.<p> 303 * 304 * @return the database connection 305 * @throws SQLException if something goes wrong 306 */ 307 public Connection getConnection() throws SQLException { 308 309 try { 310 return m_dataSource.getConnection(); 311 } catch (SQLTransientConnectionException | SQLTimeoutException e) { 312 LOG.error(e.getLocalizedMessage(), e); 313 throw e; 314 } 315 } 316 317 /** 318 * Gets the number of idle connections.<p> 319 * 320 * @return the number of idle connections 321 */ 322 public int getIdleConnections() { 323 324 return m_dataSource.getHikariPoolMXBean().getIdleConnections(); 325 } 326 327 /** 328 * Gets the pool url.<p> 329 * 330 * @return the pool url 331 */ 332 public String getPoolUrl() { 333 334 return m_poolUrl; 335 } 336}