/** * Default Jive connection PRovider. It uses the Excellent connection pool * available from http://www.javaexchange.com. This connection provider is a * a good choice unless you can use a container-managed one. */ public class DbConnectionDefaultPool extends DbConnectionProvider {
private static final String NAME = "Default Connection Pool"; private static final String DESCRipTION = "The default connection provider " + "that uses the connection pool from javaexchange.com. It works with " + "almost any database setup, is customizable, and offers good performance. " + "Use this connection provider unless you have your own or can use a " + "container managed connection pool."; private static final String AUTHOR = "CoolServlets.com"; private static final int MAJOR_VERSION = 1; private static final int MINOR_VERSION = 0; private static final boolean POOLED = true;
public DbConnectionDefaultPool() { //this.manager = manager; props = new Properties(); propDescriptions = new Properties(); //Initialize all property values initializeProperties(); //Load any existing property values loadProperties(); }
/** * Returns a database connection. */ public Connection getConnection() { if (connectionPool == null) { //block until the init has been done synchronized(initLock) { //if still null, something has gone wrong if (connectionPool == null) { System.err.println("Warning: DbConnectionDefaultPool.getConnection() was " + "called when the internal pool has not been initialized."); return null; } } } return new ConnectionWrapper(connectionPool.getConnection(), connectionPool); }
/** * Starts the pool. */ protected void start() { //acquire lock so that no connections can be returned. synchronized (initLock) { //Get properties String driver = props.getProperty("driver"); String server = props.getProperty("server"); String username = props.getProperty("username"); String passWord = props.getProperty("password"); int minConnections = 0, maxConnections = 0; double connectionTimeout = 0.0; try { minConnections = Integer.parseInt(props.getProperty("minConnections")); maxConnections = Integer.parseInt(props.getProperty("maxConnections")); connectionTimeout = Double.parseDouble(props.getProperty("connectionTimeout")); } catch (Exception e) { System.err.println("Error: could not parse default pool properties. " + "Make sure the values exist and are correct."); e.printStackTrace(); return; } String logPath = props.getProperty("logPath");
/** * Restarts the pool to take into account any property changes. */ protected void restart() { //Kill off pool. destroy(); //Reload properties. loadProperties(); //Start a new pool. start(); }
/** * Destroys the connection pool. */ protected void destroy() { if (connectionPool != null) { try { connectionPool.destroy(1); } catch (Exception e) { e.printStackTrace(); } } //Release reference to connectionPool connectionPool = null; }
/** * Returns the value of a property of the connection provider. * * @param name the name of the property. * @returns the value of the property. */ public String getProperty(String name) { return (String)props.get(name); }
/** * Returns the description of a property of the connection provider. * * @param name the name of the property. * @return the description of the property. */ public String getPropertyDescription(String name) { return (String)propDescriptions.get(name); }
/** * Returns an enumeration of the property names for the connection provider. */ public Enumeration propertyNames() { return props.propertyNames(); }
/** * Sets a property of the connection provider. Each provider has a set number * of properties that are determined by the author. Trying to set a non- * existant property will result in an IllegalArgumentException. * * @param name the name of the property to set. * @param value the new value for the property. * */ public void setProperty(String name, String value) { props.put(name, value); saveProperties(); }
/** * Give default values to all the properties and descriptions. */ private void initializeProperties() { props.put("driver",""); props.put("server",""); props.put("username",""); props.put("password",""); props.put("minConnections",""); props.put("maxConnections",""); props.put("logPath",""); props.put("connectionTimeout","");
propDescriptions.put("driver","JDBC driver. e.g. ′Oracle.jdbc.driver.OracleDriver′"); propDescriptions.put("server","JDBC connect string. e.g. ′jdbc:oracle:thin:@203.92.21.109:1526:orcl′"); propDescriptions.put("username","Database username. e.g. ′Scott′"); propDescriptions.put("password","Database password. e.g. ′Tiger′"); propDescriptions.put("minConnections","Minimum # of connections to start with in pool. Three is the recommended minimum"); propDescriptions.put("maxConnections","Maximum # of connections in dynamic pool. Fifteen should give good performance for an average load."); propDescriptions.put("logPath","Absolute path name for log file. e.g. ′c:/logs/jiveDbLog.log′"); propDescriptions.put("connectionTimeout","Time in days between connection resets. e.g. ′.5′"); }
/** * Creates a new Connection Broker<br> * dbDriver: JDBC driver. e.g. ′oracle.jdbc.driver.OracleDriver′<br> * dbServer: JDBC connect string. e.g. ′jdbc:oracle:thin:@203.92.21.109:1526:orcl′<br> * dbLogin: Database login name. e.g. ′Scott′<br> * dbPassword: Database password. e.g. ′Tiger′<br> * minConns: Minimum number of connections to start with.<br> * maxConns: Maximum number of connections in dynamic pool.<br> * logFileString: Absolute path name for log file. e.g. ′c: empmylog.log′ <br> * maxConnTime: Time in days between connection resets. (Reset does a basic cleanup)<br> */ public ConnectionPool (String dbDriver, String dbServer, String dbLogin, String dbPassword, int minConns, int maxConns, String logFileString, double maxConnTime) throws IOException { connPool = new Connection[maxConns]; connStatus = new int[maxConns]; connLockTime = new long[maxConns]; connCreateDate = new long[maxConns]; connID = new String[maxConns]; currConnections = minConns; this.maxConns = maxConns; this.dbDriver = dbDriver; this.dbServer = dbServer; this.dbLogin = dbLogin; this.dbPassword = dbPassword; this.logFileString = logFileString; maxConnMSec = (int)(maxConnTime * 86400000.0); //86400 sec/day if(maxConnMSec < 30000) { // Recycle no less than 30 seconds. maxConnMSec = 30000; }
try { log = new PrintWriter(new FileOutputStream(logFileString),true);
// Can′t open the requested file. Open the default file. } catch (IOException e1) { System.err.println("Warning: DbConnectionDefaultPool could not open "" + logFileString + "" to write log to. Make sure that your Java " + "process has permission to write to the file and that the Directory exists." ); try { log = new PrintWriter(new FileOutputStream("DCB_" + System.currentTimeMillis() + ".log"), true ); } catch (IOException e2) { throw new IOException("Can′t open any log file"); } }
// Write the pid file (used to clean up dead/broken connection) SimpleDateFormat formatter = new SimpleDateFormat ("yyyy.MM.dd G ′at′ hh:mm:ss a zzz"); java.util.Date nowc = new java.util.Date(); pid = formatter.format(nowc);
BufferedWriter pidout = new BufferedWriter(new FileWriter(logFileString + "pid")); pidout.write(pid); pidout.close();
// Initialize the pool of connections with the mininum connections: // Problems creating connections may be caused during reboot when the // servlet is started before the database is ready. Handle this // by waiting and trying again. The loop allows 5 minutes for // db reboot. boolean connectionsSUCceeded=false; int dbLoop=20;
try { for(int i=1; i < dbLoop; i++) { try { for(int j=0; j < currConnections; j++) { log.println("Create Conn "+j); createConn(j); } connectionsSucceeded=true; break; } catch (SQLException e){ log.println("--->Attempt (" + String.valueOf(i) + " of " + String.valueOf(dbLoop) + ") failed to create new connections set at startup: "); log.println(" " + e); log.println(" Will try again in 15 seconds..."); try { Thread.sleep(15000); } catch(InterruptedException e1) {} } } if(!connectionsSucceeded) { // All attempts at connecting to db exhausted log.println(" All attempts at connecting to Database exhausted"); throw new IOException(); } } catch (Exception e) { throw new IOException(); }
// Fire up the background housekeeping thread
runner = new Thread(this); runner.start();
} //End ConnectionPool()
//文件:DbConnectionDefaultPool.java的第二部分 /** * Housekeeping thread. Runs in the background with low CPU overhead. * Connections are checked for warnings and closure and are periodically * restarted. * This thread is a catchall for corrupted * connections and prevents the buildup of open cursors. (Open cursors * result when the application fails to close a Statement). * This method acts as fault tolerance for bad connection/statement programming. */ public void run() { boolean forever = true; Statement stmt=null; String currCatalog=null;
while(forever) {
// Make sure the log file is the one this instance opened // If not, clean it up! try { BufferedReader in = new BufferedReader(new FileReader(logFileString + "pid")); String curr_pid = in.readLine(); if(curr_pid.equals(pid)) { //log.println("They match = " + curr_pid); } else { //log.println("No match = " + curr_pid); log.close();
// Close all connections silently - they are definitely dead. for(int i=0; i < currConnections; i++) { try { connPool[i].close(); } catch (SQLException e1) {} // ignore } // Returning from the run() method kills the thread return; } in.close(); } catch (IOException e1) { log.println("Can′t read the file for pid info: " + logFileString + "pid"); }
// Get any Warnings on connections and print to event file for(int i=0; i < currConnections; i++) { try { currSQLWarning = connPool[i].getWarnings(); if(currSQLWarning != null) { log.println("Warnings on connection " + String.valueOf(i) + " " + currSQLWarning); connPool[i].clearWarnings(); } } catch(SQLException e) { log.println("Cannot access Warnings: " + e); } }
for(int i=0; i < currConnections; i++) { // Do for each connection long age = System.currentTimeMillis() - connCreateDate[i];
synchronized(connStatus) { if(connStatus[i] > 0) { // In use, catch it next time! continue; } connStatus[i] = 2; // Take offline (2 indicates housekeeping lock) }
try { // Test the connection with createStatement call if(age > maxConnMSec) { // Force a reset at the max conn time throw new SQLException(); }
stmt = connPool[i].createStatement(); connStatus[i] = 0; // Connection is O.K. //log.println("Connection confirmed for conn = " + // String.valueOf(i));
// Some DBs return an object even if DB is shut down if(connPool[i].isClosed()) { throw new SQLException(); } // Connection has a problem, restart it } catch(SQLException e) { try { log.println(new Date().toString() + " ***** Recycling connection " + String.valueOf(i) + ":");
try { Thread.sleep(20000); } // Wait 20 seconds for next cycle catch(InterruptedException e) { // Returning from the run method sets the internal // flag referenced by Thread.isAlive() to false. // This is required because we don′t use stop() to // shutdown this thread. return; } } } // End run
/** * This method hands out the connections in round-robin order. * This prevents a faulty connection from locking * up an application entirely. A browser ′refresh′ will * get the next connection while the faulty * connection is cleaned up by the housekeeping thread. * * If the min number of threads are ever exhausted, new * threads are added up the the max thread count. * Finally, if all threads are in use, this method waits * 2 seconds and tries again, up to ten times. After that, it * returns a null. */ public Connection getConnection() {
/** * Frees a connection. Replaces connection back into the main pool for * reuse. */ public String freeConnection(Connection conn) { String res="";
int thisconn = idOfConnection(conn); if(thisconn >= 0) { connStatus[thisconn]=0; res = "freed " + conn.toString(); //log.println("Freed connection " + String.valueOf(thisconn) + // " normal exit: "); } else { log.println("----> Could not free connection!!!"); }
return res; }
//文件:DbConnectionDefaultPool.java的第三部分 /** * Returns the age of a connection -- the time since it was handed out to * an application. */ public long getAge(Connection conn) { // Returns the age of the connection in millisec. int thisconn = idOfConnection(conn); return System.currentTimeMillis() - connLockTime[thisconn]; }
private void createConn(int i) throws SQLException { Date now = new Date(); try { Class.forName (dbDriver); Properties dbProp = new Properties(); //log.println("Creating....."); dbProp.put("user", dbLogin); dbProp.put("password", dbPassword); dbProp.put("characterEncoding","gb2112"); //dbProp.put("useUnicode", "true");
/** * Shuts down the housekeeping thread and closes all connections * in the pool. Call this method from the destroy() method of the servlet. */
/** * Multi-phase shutdown. having following sequence: * <OL> * <LI><code>getConnection()</code> will refuse to return connections. * <LI>The housekeeping thread is shut down.<br> * Up to the time of <code>millis</code> milliseconds after shutdown of * the housekeeping thread, <code>freeConnection()</code> can still be * called to return used connections. * <LI>After <code>millis</code> milliseconds after the shutdown of the * housekeeping thread, all connections in the pool are closed. * <LI>If any connections were in use while being closed then a * <code>SQLException</code> is thrown. * <LI>The log is closed. * </OL><br> * Call this method from a servlet destroy() method. * * @param millis the time to wait in milliseconds. * @exception SQLException if connections were in use after * <code>millis</code>. */ public void destroy(int millis) throws SQLException {
// Checking for invalid negative arguments is not necessary, // Thread.join() does this already in runner.join().
// Stop issuing connections available=false;
// Shut down the background housekeeping thread runner.interrupt();
// Wait until the housekeeping thread has died. try { runner.join(millis); } catch(InterruptedException e){} // ignore
// The housekeeping thread could still be running // (e.g. if millis is too small). This case is ignored. // At worst, this method will throw an exception with the // clear indication that the timeout was too short.
long startTime=System.currentTimeMillis();
// Wait for freeConnection() to return any connections // that are still used at this time. int useCount; while((useCount=getUseCount())>0 && System.currentTimeMillis() - startTime <= millis) { try { Thread.sleep(500); } catch(InterruptedException e) {} // ignore }
// Close all connections, whether safe or not for(int i=0; i < currConnections; i++) { try { connPool[i].close(); } catch (SQLException e1) { log.println("Cannot close connections on Destroy"); } }
if(useCount > 0) { //BT-test successful String msg="Unsafe shutdown: Had to close "+useCount+ " active DB connections after "+millis+"ms"; log.println(msg); // Close all open files log.close(); // Throwing following Exception is essential because servlet authors // are likely to have their own error logging requirements. throw new SQLException(msg); }
// Close all open files log.close();
}//End destroy()
/** * Less safe shutdown. Uses default timeout value. * This method simply calls the <code>destroy()</code> method * with a <code>millis</code> * value of 10000 (10 seconds) and ignores <code>SQLException</code> * thrown by that method. * @see #destroy(int) */ public void destroy() { try { destroy(10000); } catch(SQLException e) {} }
/** * Returns the number of connections in use. */ // This method could be reduced to return a counter that is // maintained by all methods that update connStatus. // However, it is more efficient to do it this way because: // Updating the counter would put an additional burden on the most // frequently used methods; in comparison, this method is // rarely used (although essential). public int getUseCount() { int useCount=0; synchronized(connStatus) { for(int i=0; i < currConnections; i++) { if(connStatus[i] > 0) { // In use useCount++; } } } return useCount; }//End getUseCount()
/** * Returns the number of connections in the dynamic pool. */ public int getSize() { return currConnections; }//End getSize()
}
/** * An implementation of the Connection interface that wraps an underlying * Connection object. It releases the connection back to a connection pool * when Connection.close() is called. */ public class ConnectionWrapper implements Connection {
/** * Instead of closing the underlying connection, we simply release * it back into the pool. */ public void close() throws SQLException { connectionPool.freeConnection(this.connection); //Release object references. Any further method calls on the //connection will fail. connection = null; connectionPool = null; }
public Statement createStatement() throws SQLException { return connection.createStatement(); }
public PreparedStatement prepareStatement(String sql) throws SQLException { return connection.prepareStatement(sql); }
public CallableStatement prepareCall(String sql) throws SQLException { return connection.prepareCall(sql); }
public String nativeSQL(String sql) throws SQLException { return connection.nativeSQL(sql); }
public void setAutoCommit(boolean autoCommit) throws SQLException { connection.setAutoCommit(autoCommit); }
public boolean getAutoCommit() throws SQLException { return connection.getAutoCommit(); }
public void commit() throws SQLException { connection.commit(); }
public void rollback() throws SQLException { connection.rollback(); }
public boolean isClosed() throws SQLException { return connection.isClosed(); }
public DatabaseMetaData getMetaData() throws SQLException { return connection.getMetaData(); }
public void setReadOnly(boolean readOnly) throws SQLException { connection.setReadOnly(readOnly); }
public boolean isReadOnly() throws SQLException { return connection.isReadOnly(); }
public void setCatalog(String catalog) throws SQLException { connection.setCatalog(catalog); }
public String getCatalog() throws SQLException { return connection.getCatalog(); }
public void setTransactionIsolation(int level) throws SQLException { connection.setTransactionIsolation(level); }
public int getTransactionIsolation() throws SQLException { return connection.getTransactionIsolation(); }
public SQLWarning getWarnings() throws SQLException { return connection.getWarnings(); }
public void clearWarnings() throws SQLException { connection.clearWarnings(); }
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return connection.createStatement(resultSetType, resultSetConcurrency); }
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return connection.prepareStatement(sql, resultSetType, resultSetConcurrency); }
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return prepareCall(sql, resultSetType, resultSetConcurrency); }
public Map getTypeMap() throws SQLException { return connection.getTypeMap(); }
public void setTypeMap(Map map) throws SQLException { connection.setTypeMap(map); }
/** * Returns a database connection from the currently active connection * provider. */ public static Connection getConnection() { if (connectionProvider == null) { synchronized (providerLock) { if (connectionProvider == null) { //Create the connection provider -- for now, this is hardcoded. For //the next beta, I′ll change this to load up the provider dynamically. connectionProvider = new DbConnectionDefaultPool(); connectionProvider.start(); } } } Connection con = connectionProvider.getConnection(); if (con == null) { System.err.println("WARNING: DbConnectionManager.getConnection() failed to obtain a connection."); } return con; }
/** * Returns the current connection provider. The only case in which this * method should be called is if more information about the current * connection provider is needed. Database connections should always be * obtained by calling the getConnection method of this class. */ public static DbConnectionProvider getDbConnectionProvider() { return connectionProvider; }
/** * Sets the connection provider. The old provider (if it exists) is shut * down before the new one is started. A connection provider <b>should * not</b> be started before being passed to the connection manager. */ public static void setDbConnectionProvider(DbConnectionProvider provider) { synchronized (providerLock) { if (connectionProvider != null) { connectionProvider.destroy(); connectionProvider = null; } connectionProvider = provider; provider.start(); } }
/** Dummy values. Override in subclasses. **/ private static final String NAME = ""; private static final String DESCRIPTION = ""; private static final String AUTHOR = ""; private static final int MAJOR_VERSION = 0; private static final int MINOR_VERSION = 0; private static final boolean POOLED = false;
/** * Returns the name of the connection provider. */ public String getName() { return NAME; }
/** * Returns a description of the connection provider. */ public String getDescription() { return DESCRIPTION; }
/** * Returns the author of the connection provider. */ public String getAuthor() { return AUTHOR; }
/** * Returns the major version of the connection provider, i.e. the 1 in 1.0. */ public int getMajorVersion() { return MAJOR_VERSION; }
public int getMinorVersion() {
return MINOR_VERSION;
}
/** * Returns true if this connection provider provides connections out * of a connection pool. */ public boolean isPooled() { return POOLED; }
/** * Returns a database connection. When a Jive component is done with a * connection, it will call the close method of that connection. Therefore, * connection pools with special release methods are not directly * supported by the connection provider infrastructure. Instead, connections * from those pools should be wrapped such that calling the close method * on the wrapper class will release the connection from the pool. */ public abstract Connection getConnection();
/** * Starts the connection provider. For some connection providers, this * will be a no-op. However, connection provider users should always call * this method to make sure the connection provider is started. */ protected abstract void start();
/** * This method should be called whenever properties have been changed so * that the changes will take effect. */ protected abstract void restart();
/** * Tells the connection provider to destroy itself. For many connection * providers, this will essentially result in a no-op. However, * connection provider users should always call this method when changing * from one connection provider to another to ensure that there are no * dangling database connections. */ protected abstract void destroy();
/** * Returns the value of a property of the connection provider. * * @param name the name of the property. * @returns the value of the property. */ public abstract String getProperty(String name);
/** * Returns the description of a property of the connection provider. * * @param name the name of the property. * @return the description of the property. */ public abstract String getPropertyDescription(String name);
/** * Returns an enumeration of the property names for the connection provider. */ public abstract Enumeration propertyNames();
/** * Sets a property of the connection provider. Each provider has a set number * of properties that are determined by the author. Trying to set a non- * existant property will result in an IllegalArgumentException. * * @param name the name of the property to set. * @param value the new value for the property. */ public abstract void setProperty(String name, String value);
/** * Manages properties for the entire Jive system. Properties are merely * pieces of information that need to be saved in between server restarts. * <p> * At the moment, properties are stored in a Java Properties file. In a version * of Jive coming soon, the properties file format will move to xml. XML * properties will allow hierarchical property structures which may mean the * API of this class will have to change. * <p> * Jive properties are only meant to be set and retrevied by core Jive classes. * Therefore, SKIN writers should probably ignore this class. * <p> * This class is implemented as a singleton since many classloaders seem to * take issue with doing classpath resource loading from a static context. */ public class PropertyManager {
/** * Returns a Jive property * * @param name the name of the property to return. * @returns the property value specified by name. */ public static String getProperty(String name) { if (manager == null) { synchronized(managerLock) { if (manager == null) { String sysname=System.getProperty("os.name").toUpperCase(); if(sysname.indexOf("WIN")!=-1){ propsName=propsName2000; } else{ propsName=propsNameLinux; } manager = new PropertyManager(propsName); } } } return manager.getProp(name); }
/** * Sets a Jive property. * * @param name the name of the property being set. * @param value the value of the property being set. */ public static void setProperty(String name, String value) { if (manager == null) { synchronized(managerLock) { if (manager == null) { manager = new PropertyManager(propsName); } } } manager.setProp(name, value); }
/** * Returns true if the properties are readable. This method is mainly * valuable at setup time to ensure that the properties file is setup * correctly. */ public static boolean propertyFileIsReadable() { if (manager == null) { synchronized(managerLock) { if (manager == null) { manager = new PropertyManager(propsName); } } } return manager.propFileIsReadable(); }
/** * Returns true if the properties are writable. This method is mainly * valuable at setup time to ensure that the properties file is setup * correctly. */ public static boolean propertyFileIsWritable() { if (manager == null) { synchronized(managerLock) { if (manager == null) { manager = new PropertyManager(propsName); } } } return manager.propFileIsWritable(); }
/** * Returns true if the jive.properties file exists where the path property * purports that it does. */ public static boolean propertyFileExists() { if (manager == null) { synchronized(managerLock) { if (manager == null) { manager = new PropertyManager(propsName); } } } return manager.propFileExists(); }
/** * Gets a Jive property. Jive properties are stored in jive.properties. * The properties file should be accesible from the classpath. Additionally, * it should have a path field that gives the full path to where the * file is located. Getting properties is a fast Operation. */ public String getProp(String name) { //If properties aren′t loaded yet. We also need to make this thread //safe, so synchronize... if (properties == null) { synchronized(propertiesLock) { //Need an additional check if (properties == null) { loadProps(); } } } return properties.getProperty(name); }
/** * Sets a Jive property. Because the properties must be saved to disk * every time a property is set, property setting is relatively slow. */ public void setProp(String name, String value) { //Only one thread should be writing to the file system at once. synchronized (propertiesLock) { //Create the properties object if necessary. if (properties == null) { loadProps(); } properties.setProperty(name, value); //Now, save the properties to disk. In order for this to work, the user //needs to have set the path field in the properties file. Trim //the String to make sure there are no extra spaces. String path = properties.getProperty("path").trim(); OutputStream out = null; try { out = new FileOutputStream(path); properties.store(out, "jive.properties -- " + (new java.util.Date())); } catch (Exception ioe) { System.err.println("There was an error writing jive.properties to " + path + ". " + "Ensure that the path exists and that the Jive process has permission " + "to write to it -- " + ioe); ioe.printStackTrace(); } finally { try { out.close(); } catch (Exception e) { } } } }
/** * Returns true if the properties are readable. This method is mainly * valuable at setup time to ensure that the properties file is setup * correctly. */ public boolean propFileIsReadable() { try { InputStream in = getClass().getResourceAsStream(resourceURI); return true; } catch (Exception e) { return false; } }
/** * Returns true if the jive.properties file exists where the path property * purports that it does. */ public boolean propFileExists() { String path = getProp("path"); File file = new File(path); if (file.isFile()) { return true; } else { return false; } }
/** * Returns true if the properties are writable. This method is mainly * valuable at setup time to ensure that the properties file is setup * correctly. */ public boolean propFileIsWritable() { String path = getProp("path"); File file = new File(path); if (file.isFile()) { //See if we can write to the file if (file.canWrite()) { return true; } else { return false; } } else { return false; } }