/**
* Copyright (C) 2012 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.dashboard.database;
import org.jboss.dashboard.commons.misc.ReflectionUtils;
import org.jboss.dashboard.error.ErrorManager;
import org.jboss.dashboard.profiler.CodeBlockTrace;
import org.jboss.dashboard.profiler.CodeBlockType;
import org.jboss.dashboard.profiler.CoreCodeBlockTypes;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
/**
* A data source implementation that bounds every connection to the underlying thread.
*/
public class NonPooledDataSource implements DataSource {
private static transient Log log = LogFactory.getLog(NonPooledDataSource.class.getName());
protected PrintWriter printWriter;
protected int loginTimeOut;
// Data source properties
protected String url;
protected String user;
protected String password;
protected String driver;
protected int isolation;
protected boolean autoCommit;
public NonPooledDataSource() {
this.loginTimeOut = 0;
this.printWriter = new PrintWriter(System.out);
this.autoCommit = false;
this.isolation = Connection.TRANSACTION_SERIALIZABLE;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public int getIsolation() {
return isolation;
}
public void setIsolation(int isolation) {
this.isolation = isolation;
}
public boolean isAutoCommit() {
return autoCommit;
}
public void setAutoCommit(boolean autoCommit) {
this.autoCommit = autoCommit;
}
// javax.sql.DataSource implementation
public int getLoginTimeout() throws SQLException {
return loginTimeOut;
}
public void setLoginTimeout(int seconds) throws SQLException {
this.loginTimeOut = seconds;
}
public PrintWriter getLogWriter() throws SQLException {
return printWriter;
}
public void setLogWriter(PrintWriter out) throws SQLException {
this.printWriter = out;
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
public Connection getConnection(String username, String password) throws SQLException {
return getConnection();
}
public Connection getConnection() throws SQLException {
try {
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, user, password);
setAutoCommit(conn, autoCommit);
setIsolation(conn, isolation);
return createConnectionProxy(conn);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public Logger getParentLogger() {
return null;
}
protected boolean getAutoCommit(Connection conn) {
try {
return conn.getAutoCommit();
} catch (SQLException e) {
// Ignore problems when trying to get autocommit.
// In some environments (Presidencia - Informix) when trying to get autocommit an exception is thrown.
log.debug("Can not get autocommit.", e);
return true;
}
}
protected void setAutoCommit(Connection conn, boolean autocommit) {
try {
if (getAutoCommit(conn) != autocommit) {
conn.setAutoCommit(autocommit);
}
} catch (SQLException e) {
// Ignore problems when trying to change autocommit.
// In some environments (Presidencia - Informix) when trying to set autocommit=true an exception is thrown.
log.debug("Can not set autocommit.", e);
}
}
protected void setIsolation(Connection conn,int isolation) {
try {
if (conn.getTransactionIsolation() != isolation) {
conn.setTransactionIsolation(isolation);
}
} catch (SQLException e) {
log.debug("Can not set connection isolation.", e);
}
}
// java.sql.Connection proxy
public Connection createConnectionProxy(Connection conn) throws SQLException {
return (Connection) Proxy.newProxyInstance(
conn.getClass().getClassLoader(),
getClassInterfaces(conn.getClass()),
new ConnectionInvocationHandler(conn));
}
private class ConnectionInvocationHandler implements InvocationHandler {
private Connection conn = null;
public ConnectionInvocationHandler(Connection conn) {
this.conn = conn;
}
public Object invoke(Object proxy, final Method m, final Object[] args) throws Throwable {
// Capture commit.
if (m.getName().equals("commit")) {
CodeBlockTrace trace = new SQLStatementTrace("commit").begin();
try {
return m.invoke(conn, args);
} catch (Throwable e) {
ErrorManager.lookup().notifyError(e, true);
throw e;
} finally {
trace.end();
}
}
// Capture rollback.
if (m.getName().equals("rollback")) {
CodeBlockTrace trace = new SQLStatementTrace("rollback").begin();
try {
return m.invoke(conn, args);
} catch (Throwable e) {
ErrorManager.lookup().notifyError(e, true);
throw e;
} finally {
trace.end();
}
}
// Capture Statement creation.
Object result = m.invoke(conn, args);
if (m.getReturnType() != null) {
if (m.getReturnType().equals(PreparedStatement.class)) {
String sql = (String) args[0];
return createPreparedStatementProxy((PreparedStatement) result, sql);
}
if (m.getReturnType().equals(Statement.class)) {
return createStatementProxy((Statement) result);
}
}
return result;
}
}
// java.sql.Statement proxy
/** A cache of java class interfaces */
protected transient Map<Class,Class[]> _classInterfacesMap = new HashMap<Class,Class[]>();
protected Class[] getClassInterfaces(Class clazz) {
Class[] result = _classInterfacesMap.get(clazz);
if (result != null) return result;
_classInterfacesMap.put(clazz, result = ReflectionUtils.getClassHierarchyInterfaces(clazz));
return result;
}
protected Statement createStatementProxy(Statement stmt) throws SQLException {
return (Statement)Proxy.newProxyInstance(
stmt.getClass().getClassLoader(),
getClassInterfaces(stmt.getClass()),
new StatementInvocationHandler(stmt));
}
protected Statement createPreparedStatementProxy(PreparedStatement stmt, String sql) throws SQLException {
return (Statement)Proxy.newProxyInstance(
stmt.getClass().getClassLoader(),
getClassInterfaces(stmt.getClass()),
new PreparedStatementInvocationHandler(stmt, sql));
}
static class StatementInvocationHandler implements InvocationHandler {
protected Statement stmt;
public StatementInvocationHandler(Statement stmt) {
this.stmt = stmt;
}
public Object invoke(Object proxy, final Method m, final Object[] args) throws Throwable {
if (m.getName().startsWith("execute") && args != null && args.length > 0) {
String sqlToExec = (String) args[0];
CodeBlockTrace trace = new SQLStatementTrace(sqlToExec).begin();
try {
if (log.isDebugEnabled()) log.debug(sqlToExec);
return m.invoke(stmt, args);
} catch (Throwable e) {
ErrorManager.lookup().notifyError(e, true);
throw e;
} finally {
trace.end();
}
} else {
return m.invoke(stmt, args);
}
}
}
static class PreparedStatementInvocationHandler extends StatementInvocationHandler {
protected String sql;
public PreparedStatementInvocationHandler(Statement stmt, String sql) {
super(stmt);
this.sql = sql;
}
public Object invoke(Object proxy, final Method m, final Object[] args) throws Throwable {
if (m.getName().startsWith("execute")) {
String sqlToExec = args != null && args.length > 0 ? (String) args[0] : sql;
CodeBlockTrace trace = new SQLStatementTrace(sqlToExec).begin();
try {
if (log.isDebugEnabled()) log.debug(sqlToExec);
return m.invoke(stmt, args);
} catch (Throwable e) {
ErrorManager.lookup().notifyError(e, true);
throw e;
} finally {
trace.end();
}
} else {
return m.invoke(stmt, args);
}
}
}
static class SQLStatementTrace extends CodeBlockTrace {
protected Map<String,Object> context;
public SQLStatementTrace(String sql) {
super(stripAfterWhere(sql));
context = new HashMap<String,Object>();
context.put("SQL", sql);
}
public CodeBlockType getType() {
return CoreCodeBlockTypes.SQL;
}
public String getDescription() {
return (String) context.get("SQL");
}
public Map<String,Object> getContext() {
return context;
}
/**
* To group sensibly and to avoid recording sensitive data, Don't record the where clause
* (only used for dynamic SQL since parameters aren't included in prepared statements)
* @return subset of passed SQL up to the where clause.
*/
public static String stripAfterWhere(String sql) {
for (int i=0; i<sql.length()-4; i++) {
if (sql.charAt(i)=='w' || sql.charAt(i)=='W') {
if (sql.substring(i+1, i+5).equalsIgnoreCase("here")) {
return sql.substring(0, i);
}
}
}
return sql;
}
}
}