/* * File : ConnectionBeanProvider.java * Created : 21-oct-2003 18:18 * By : fbusquets * * JClic - Authoring and playing system for educational activities * * Copyright (C) 2000 - 2018 Francesc Busquets & Departament * d'Educacio de la Generalitat de Catalunya * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details (see the LICENSE file). */ package edu.xtec.util.db; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.HashMap; import java.util.Map; /** * Classes that extended this abstract class provide {@link edu.xtec.util.db.ConnectionBean} * objects to clients. It also provides a static method to create ConnectionBeanProvider objects * based on different parameters, and stores a map containing all the created * objects, in order to reuse them between different instances or applications * sharing the same JVM. * @author Francesc Busquets (fbusquets@xtec.cat) * @version 13.09.16 */ public abstract class ConnectionBeanProvider { /** Tag used in properties to identify the JDBC driver used in the connections * to the database. */ public static final String DB_DRIVER="dbDriver"; /** Tag used in properties to identify the server used in connections */ public static final String DB_SERVER="dbServer"; /** Tag used in properties to identify the login name used in connections. */ public static final String DB_LOGIN="dbLogin"; /** Tag used in properties to identify the password used in connections. */ public static final String DB_PASSWORD="dbPassword"; /** * Tag used in properties to determine if the {@link edu.xtec.util.db.ConnectionBean} * objects created must store and reuse {@link java.sql.PreparedStatement} objects. */ public static final String MAP_STATEMENTS="dbMapStatements"; /** Tag used for true */ public static final String TRUE = "true"; /** Tag used for false */ public static final String FALSE="false"; /** * Name of the JDBC driver used in Connection objects */ protected String dbDriver; /** * Name of the JDBC url used in Connection objects */ protected String dbServer; /** Name of the user used in Connection objects. */ protected String dbLogin; /** * Password used in JDBC Connection objects. */ protected String dbPassword; /** * When true, the {@link edu.xtec.util.db.ConnectionBean} objects created will store and reuse * prepared statements. */ protected boolean mapStatements; private int useCount; private static Map cbProviders; /** * Main initialization function. Must be called immediatelly after constructor by * getConnectionBeanProvider functions. * @param map Collection of key - value pairs that must specify the driver, url, login and * password of the just created ConnectionBeanProvider. * @throws Exception Throwed if dbDriver does not contain a valid driver name, or if it can't be * instantiated. */ protected void setUp(Map map) throws Exception{ dbDriver = getValue(map, DB_DRIVER, null); dbServer = getValue(map, DB_SERVER, null); dbLogin = getValue(map, DB_LOGIN, null); dbPassword = getValue(map, DB_PASSWORD, null); mapStatements = !FALSE.equals(getValue(map, MAP_STATEMENTS, TRUE)); useCount=0; } /** This is the main function that all ConnectionbeanProvider objects must * implement.

* Important: You must ever call FreeConnectionBean after the use of the ConnectionBean * object. Typical inmplementation use a try - catch - finally statement block in * order to ensure that all ConnectionBean objects will be properly disposed after * use.

* Example:

*

     * ConectionBeanProvider cbp;
     * java.util.Properties prop=new Java.util.Properties();
     * // ...
     * // ... fill-up properties with values for dbDriver, dbServer, dbLogin, etc.
     * // ...
     * cbp=ConnectionBeanProvider.getConnectionBeanProvider(map);
     * ConnectionBean cb=cbp.getConnectionBean();
     * try {
     *  // ... use of the ConnectionBean object
     * } catch(Exception ex){
     *  // ... process possible exceptions done while database access
     * } finally {
     *  // Very important: free the ConnectionBean object:
     *  cbp.freeConnectionBean(cb);
     * }
     * 
* @return A ready-to-use ConnectionBean. Remember to return it by calling * FreeConnectionBean. */ public abstract ConnectionBean getConnectionBean(); /** This method must be called when the obtained ConnectionBean is no longer needed, * usualy inside the finally block of a try - catch statement. * @param conn The ConnectionBean object to be disposed * @return A descriptive String, useful only for debug purposes. */ public abstract String freeConnectionBean(ConnectionBean conn); /** * This method will be called before finalization. Implementations of * ConnectionBeanProvider must perform cleanup operations on all opened * connections. */ protected abstract void destroy(); /** * Provides information about the current state of this ConnectionBeanProvider. * @return Information string, formatted in HTML. */ public String getInfo(){ StringBuilder sb=new StringBuilder(); sb.append(DB_DRIVER).append(": ").append(dbDriver).append("
\n"); sb.append(DB_SERVER).append(": ").append(dbServer).append("
\n"); sb.append(DB_LOGIN).append(": ").append(dbLogin).append("
\n"); sb.append(MAP_STATEMENTS).append(": ").append(mapStatements).append("
\n"); sb.append("Use count:").append(useCount).append("
\n"); return sb.substring(0); } /** * Provides information about all the ConnectionBeanProvider objects currently * existing. * @return Information string, formatted in HTML. */ public static String getGlobalInfo(){ StringBuilder sb=new StringBuilder(); if(cbProviders==null) cbProviders=new HashMap(2); sb.append("").append(cbProviders.size()).append(" ConnectionBeanProviders created
\n"); for(String key : cbProviders.keySet()){ ConnectionBeanProvider cbp=cbProviders.get(key); sb.append("
\n"); sb.append(cbp.getInfo()); } return sb.substring(0); } /** * Method used to build a ConnectionBeanProvider. The current * implementations returns a {@link edu.xtec.util.db.SingleConnectionBeanProvider} if mapStatements * is false. Otherwise, it checks the value of dbDriverName to return a * {@link PooledConnectionBeanProvider} or a {@link * edu.xtec.util.db.JNDIConnectionBeanProvider}. * @param pooled Determines the type of ConnectionbeanProvider to be created: pooled or simple. * @param dbDriver Name of the JDBC driver to be used. When this param is "JNDI", a * {@link edu.xtec.util.db.JNDIConnectionBeanProvider} will be created. * @param dbServer Path to the JDBC source or JNDI datasource to be used. * @param dbLogin User name to be used in JDB connections. * @param dbPassword Password to be used in JDBC connections. * @param mapStatements When true, the created {@link edu.xtec.util.db.ConnectionBean} * objects will reuse PreparedStatements. * @throws Exception If something goes wrong. * @return The ConnectionBean object. */ public static ConnectionBeanProvider getConnectionBeanProvider(boolean pooled, String dbDriver, String dbServer, String dbLogin, String dbPassword, boolean mapStatements) throws Exception{ Map map=new HashMap(); map.put(DB_DRIVER, dbDriver); map.put(DB_SERVER, dbServer); map.put(DB_LOGIN, dbLogin); map.put(DB_PASSWORD, dbPassword); map.put(MAP_STATEMENTS, mapStatements ? TRUE : FALSE); return getConnectionBeanProvider(pooled, map); } /** * This method calls the many-params version of getConnectionBeanProvider(), * extracting the needed parameters from the map. * @param map Collection containing all the key - value pairs needed to create the * ConnectionBeanProvider object. * @param pooled Determines the type of ConnectionbeanProvider to be created: pooled or simple. * @throws Exception If something goes wrong. * @return The requested ConnectionBeanProvider object. */ public static ConnectionBeanProvider getConnectionBeanProvider(boolean pooled, Map map) throws Exception{ ConnectionBeanProvider result=null; String staticDbDriver=map.get(DB_DRIVER); String staticDbServer=map.get(DB_SERVER); String staticDbLogin=map.get(DB_LOGIN); StringBuilder kb=new StringBuilder(); kb.append(staticDbDriver).append("@"); kb.append(staticDbServer).append("@"); kb.append(staticDbLogin).append("@"); kb.append(pooled ? "POOLED" : "DIRECT"); String ks=kb.substring(0); if(cbProviders==null) cbProviders=new HashMap(2); else result = cbProviders.get(ks); /* Iterator it=cbProviders.keySet().iterator(); while(it.hasNext()){ Object o=it.next(); if(o!=null && o.equals(ks)){ result=(ConnectionBeanProvider)cbProviders.get(o); break; } } */ if(result==null){ if(JNDIConnectionBeanProvider.JNDI.equals(staticDbDriver)) result=new JNDIConnectionBeanProvider(); else if(pooled) result=new PooledConnectionBeanProvider(); else result=new SingleConnectionBeanProvider(); result.setUp(map); cbProviders.put(ks, result); } result.useCount++; result.readDBSettings(map); return result; } /** Use this method when a ConectionbeanProvider is no longer needed. * ConnectionBeanProvider objects are stored into a static collection, so they can * be reused when multiple classes (or multiple instances of the same class) need * them. The ConnectionBeanProvider object will be destroyed only when a internal * counter decreases to zero. * @param cbp The ConnectionBeanProvider to be released. */ public static void freeConnectionBeanProvider(ConnectionBeanProvider cbp){ if(cbp!=null && cbProviders!=null){ for (String s : cbProviders.keySet()) { ConnectionBeanProvider o=cbProviders.get(s); if(o!=null && o.equals(cbp) && --cbp.useCount<=0){ cbp.destroy(); cbProviders.remove(s); break; } } } } /** * Useful method used to retrieve a value from a Map using a default value when not * found, or when found with null value. * @param map The map where to search for * @param key The key to be searched * @param defaultValue Value returned when not found or null * @return The String associated with the specified key, or the defaultValue if not found. */ protected String getValue(Map map, String key, String defaultValue){ String result=map.get(key); if(result==null || result.trim().length()==0) result=defaultValue; return result; } /** * Key for the name of the database table that contains key-value pairs of settings */ public static final String SETTINGS_TABLE_KEY="SETTINGS_TABLE"; /** * Default name for the settings table */ public static final String DEFAULT_SETTINGS_TABLE = "SETTINGS"; /** * All the settings with this value will be read from the database */ public static final String READ_FROM_DATABASE="READ_FROM_DATABASE"; /** * This function looks inside a map for entries with the value "READ_FROM_DATABASE". * All settings with this text will be replaced with the value stored in a * table names "SETTINGS" (or with the name specified in the map with the key * "SETTINGS_TABLE"). This table must contain at least two fields: "SETTING_KEY" * and "SETTING_VALUE". * @param map The map to be processed * @throws Exception If the database is not accessible. */ public void readDBSettings(Map map) throws Exception{ Exception exception=null; String settingsTable=getValue(map, SETTINGS_TABLE_KEY, DEFAULT_SETTINGS_TABLE); if(settingsTable!=null && map.containsValue(READ_FROM_DATABASE)){ edu.xtec.util.db.ConnectionBean con=null; PreparedStatement stmt=null; ResultSet rs=null; try{ con=getConnectionBean(); String sql= "SELECT SETTING_VALUE"+ " FROM "+settingsTable+ " WHERE SETTING_KEY=?"; stmt=con.getPreparedStatement(sql); for (String key : map.keySet()) { String value=map.get(key); if(READ_FROM_DATABASE.equals(value)){ stmt.setString(1, key); rs=stmt.executeQuery(); if(rs.next()){ value=rs.getString(1); map.put(key, value); } } } } catch(Exception ex) { exception=ex; } finally { if(rs!=null) rs.close(); if(stmt!=null && con!=null) con.closeStatement(stmt); if(con!=null) freeConnectionBean(con); } } if(exception!=null) throw exception; } }