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}