import java.sql.SQLException;
import java.sql.Statement;
+import java.util.HashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.juli.logging.Log;
* The parent
*/
protected ConnectionPool parent;
+
+ private HashMap<Object, Object> attributes = new HashMap<Object, Object>();
/**
* Weak reference to cache the list of interceptors for this connection
public boolean isReleased() {
return released.get();
}
+
+ public HashMap<Object,Object> getAttributes() {
+ return attributes;
+ }
}
protected static final String PREPARE_STATEMENT = "prepareStatement";
protected static final int PREPARE_STATEMENT_IDX = 1;
protected static final String PREPARE_CALL = "prepareCall";
- protected static final int PREPARE_IDX = 2;
+ protected static final int PREPARE_CALL_IDX = 2;
protected static final String[] STATEMENT_TYPES = {CREATE_STATEMENT, PREPARE_STATEMENT, PREPARE_CALL};
protected static final int STATEMENT_TYPE_COUNT = STATEMENT_TYPES.length;
}else if (compare(PREPARE_CALL,name)) {
//prepareCall
sql = (String)args[0];
- constructor = getConstructor(PREPARE_IDX,CallableStatement.class);
+ constructor = getConstructor(PREPARE_CALL_IDX,CallableStatement.class);
prepareCall(sql,time);
}else {
//do nothing, might be a future unsupported method
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
protected static final String[] CALLABLE_TYPE = new String[] {PREPARE_CALL};
protected static final String[] PREPARED_TYPE = new String[] {PREPARE_STATEMENT};
protected static final String[] NO_TYPE = new String[] {};
+
+ protected static final String STATEMENT_CACHE_ATTR = StatementCache.class.getName() + ".cache";
/*begin properties for the statement cache*/
private boolean cachePrepared = true;
private boolean cacheCallable = false;
private int maxCacheSize = 50;
- private ConnectionPool parent;
private PooledConnection pcon;
private String[] types;
+ public boolean isCachePrepared() {
+ return cachePrepared;
+ }
+
+ public boolean isCacheCallable() {
+ return cacheCallable;
+ }
+
+ public int getMaxCacheSize() {
+ return maxCacheSize;
+ }
+
+ public String[] getTypes() {
+ return types;
+ }
+
+ public AtomicInteger getCacheSize() {
+ return cacheSize;
+ }
+
public void setProperties(Map<String, InterceptorProperty> properties) {
super.setProperties(properties);
InterceptorProperty p = properties.get("prepared");
/*begin the actual statement cache*/
- private static ConcurrentHashMap<PooledConnection, ConcurrentHashMap<String,StatementProxy>> statementCache =
- new ConcurrentHashMap<PooledConnection, ConcurrentHashMap<String,StatementProxy>>();
-
-
public void reset(ConnectionPool parent, PooledConnection con) {
super.reset(parent, con);
if (parent==null) {
cacheSize = null;
- this.parent = null;
this.pcon = null;
} else {
cacheSize = cacheSizeMap.get(parent);
- this.parent = parent;
this.pcon = con;
+ if (!pcon.getAttributes().containsKey(STATEMENT_CACHE_ATTR)) {
+ ConcurrentHashMap<String,CachedStatement> cache = new ConcurrentHashMap<String, CachedStatement>();
+ pcon.getAttributes().put(STATEMENT_CACHE_ATTR,cache);
+ }
}
}
public void disconnected(ConnectionPool parent, PooledConnection con, boolean finalizing) {
- ConcurrentHashMap<String,StatementProxy> statements = statementCache.get(con);
+ ConcurrentHashMap<String,CachedStatement> statements =
+ (ConcurrentHashMap<String,CachedStatement>)con.getAttributes().get(STATEMENT_CACHE_ATTR);
+
if (statements!=null) {
- for (Map.Entry<String, StatementProxy> p : statements.entrySet()) {
+ for (Map.Entry<String, CachedStatement> p : statements.entrySet()) {
closeStatement(p.getValue());
}
statements.clear();
}
+
super.disconnected(parent, con, finalizing);
}
- public void closeStatement(StatementProxy st) {
+ public void closeStatement(CachedStatement st) {
try {
if (st==null) return;
if (((PreparedStatement)st).isClosed()) return;
}
}
+ @Override
+ protected Object createDecorator(Object proxy, Method method, Object[] args,
+ Object statement, Constructor<?> constructor, String sql)
+ throws InstantiationException, IllegalAccessException, InvocationTargetException {
+ boolean process = process(this.types, method, false);
+ if (process) {
+ Object result = null;
+ CachedStatement statementProxy = new CachedStatement((Statement)statement,sql);
+ result = constructor.newInstance(new Object[] { statementProxy });
+ statementProxy.setActualProxy(result);
+ statementProxy.setConnection(proxy);
+ statementProxy.setConstructor(constructor);
+ return result;
+ } else {
+ return super.createDecorator(proxy, method, args, statement, constructor, sql);
+ }
+ }
+
+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (compare(CLOSE_VAL,method)) {
return super.invoke(proxy, method, args);
} else {
- boolean process = false;
- process = process(this.types, method, process);
-
- if (process) {
- //check the cache
- //if (isCached) {
-
- //} else {
+ boolean process = process(this.types, method, false);
+ if (process && args.length>0 && args[0] instanceof String) {
+ CachedStatement statement = isCached((String)args[0]);
+ if (statement!=null) {
+ //remove it from the cache since it is used
+ removeStatement(statement);
+ return statement.getActualProxy();
+ } else {
return super.invoke(proxy, method, args);
- //}
+ }
} else {
return super.invoke(proxy,method,args);
}
}
}
- public boolean isCached(String sql) {
- ConcurrentHashMap<String,StatementProxy> cache = statementCache.get(pcon);
- return cache.containsKey(sql);
+ public CachedStatement isCached(String sql) {
+ ConcurrentHashMap<String,CachedStatement> cache =
+ (ConcurrentHashMap<String,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
+ return cache.get(sql);
}
- public boolean cacheStatement(StatementProxy proxy) {
- ConcurrentHashMap<String,StatementProxy> cache = statementCache.get(pcon);
+ public boolean cacheStatement(CachedStatement proxy) {
+ ConcurrentHashMap<String,CachedStatement> cache =
+ (ConcurrentHashMap<String,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
if (proxy.getSql()==null) {
return false;
} else if (cache.containsKey(proxy.getSql())) {
- cache.put(proxy.getSql(), proxy);
- return true;
+ return false;
} else if (cacheSize.get()>=maxCacheSize) {
return false;
} else if (cacheSize.incrementAndGet()>maxCacheSize) {
return false;
} else {
//cache the statement
+ cache.put(proxy.getSql(), proxy);
return true;
}
}
+
+ public boolean removeStatement(CachedStatement proxy) {
+ ConcurrentHashMap<String,CachedStatement> cache =
+ (ConcurrentHashMap<String,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
+ if (cache.remove(proxy.getSql()) != null) {
+ cacheSize.decrementAndGet();
+ return true;
+ } else {
+ return false;
+ }
+ }
/*end the actual statement cache*/
- protected class StatementProxy extends StatementDecoratorInterceptor.StatementProxy {
+ protected class CachedStatement extends StatementDecoratorInterceptor.StatementProxy<Statement> {
boolean cached = false;
- public StatementProxy(Object parent, String sql) {
+ public CachedStatement(Statement parent, String sql) {
super(parent, sql);
- cached = cacheStatement(this);
}
-
- public void closeInvoked() {
- super.closedInvoked();
- if (cached) {
+
+ @Override
+ public void closedInvoked() {
+ //should we cache it
+ boolean shouldClose = true;
+ if (cacheSize.get() < maxCacheSize) {
//cache a proxy so that we don't reuse the facade
- StatementProxy proxy = new StatementProxy(getDelegate(),getSql());
- proxy.setActualProxy(getActualProxy());
- proxy.setConnection(getConnection());
+ CachedStatement proxy = new CachedStatement(getDelegate(),getSql());
+ try {
+ //create a new facade
+ Object actualProxy = getConstructor().newInstance(new Object[] { proxy });
+ proxy.setActualProxy(actualProxy);
+ proxy.setConnection(getConnection());
+ proxy.setConstructor(getConstructor());
+ if (cacheStatement(proxy)) {
+ proxy.cached = true;
+ shouldClose = false;
+ }
+ } catch (Exception x) {
+ removeStatement(proxy);
+ }
+ }
+ closed = true;
+ delegate = null;
+ if (shouldClose) {
+ super.closedInvoked();
}
}
((Statement)getDelegate()).close();
}
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // get the name of the method for comparison
- final String name = method.getName();
- // was close invoked?
- boolean close = compare(JdbcInterceptor.CLOSE_VAL, name);
- // allow close to be called multiple times
- if (close && closed)
- return null;
- // are we calling isClosed?
- if (compare(JdbcInterceptor.ISCLOSED_VAL, name))
- return Boolean.valueOf(closed);
- // if we are calling anything else, bail out
- if (closed)
- throw new SQLException("Statement closed.");
- if (name.equals("getConnection")){
- return getConnection();
- }
- boolean process = isExecuteQuery(method);
- // check to see if we are about to execute a query
- // if we are executing, get the current time
- Object result = null;
- try {
- if (cached && close) {
- //dont invoke actual close
- } else {
- // execute the query
- result = method.invoke(delegate, args);
- }
- } catch (Throwable t) {
- if (t instanceof InvocationTargetException) {
- InvocationTargetException it = (InvocationTargetException) t;
- throw it.getCause() != null ? it.getCause() : it;
- } else {
- throw t;
- }
- }
- // perform close cleanup
- if (close) {
- closeInvoked();
- }
- if (process){
- Constructor<?> cons = getResultSetConstructor();
- result = cons.newInstance(new Object[]{new ResultSetProxy(getActualProxy(), result)});
- }
- return result;
- }
-
}
}
@Override
public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
try {
- Object result = null;
String name = method.getName();
Constructor<?> constructor = null;
String sql = null;
sql = (String)args[0];
} else if (compare(PREPARE_CALL, name)) {
// prepareCall
- constructor = getConstructor(PREPARE_IDX, CallableStatement.class);
+ constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
sql = (String)args[0];
} else {
// do nothing, might be a future unsupported method
// so we better bail out and let the system continue
return statement;
}
- StatementProxy statementProxy = new StatementProxy(statement,sql);
- result = constructor.newInstance(new Object[] { statementProxy });
- statementProxy.setActualProxy(result);
- statementProxy.setConnection(proxy);
- return result;
+ return createDecorator(proxy, method, args, statement, constructor, sql);
} catch (Exception x) {
logger.warn("Unable to create statement proxy for slow query report.", x);
}
return statement;
}
+ protected Object createDecorator(Object proxy, Method method, Object[] args,
+ Object statement, Constructor<?> constructor, String sql)
+ throws InstantiationException, IllegalAccessException, InvocationTargetException {
+ Object result = null;
+ StatementProxy statementProxy = new StatementProxy<Statement>((Statement)statement,sql);
+ result = constructor.newInstance(new Object[] { statementProxy });
+ statementProxy.setActualProxy(result);
+ statementProxy.setConnection(proxy);
+ statementProxy.setConnection(constructor);
+ return result;
+ }
+
protected boolean isExecuteQuery(String methodName) {
return EXECUTE_QUERY_TYPES[0].equals(methodName);
}
* @author fhanik
*
*/
- protected class StatementProxy implements InvocationHandler {
+ protected class StatementProxy<T extends java.sql.Statement> implements InvocationHandler {
protected boolean closed = false;
- protected Object delegate;
+ protected T delegate;
private Object actualProxy;
private Object connection;
private String sql;
+ private Constructor constructor;
- public StatementProxy(Object parent, String sql) {
- this.delegate = parent;
+ public StatementProxy(T delegate, String sql) {
+ this.delegate = delegate;
this.sql = sql;
}
- public Object getDelegate() {
+ public T getDelegate() {
return this.delegate;
}
return this.actualProxy;
}
+
+ public Constructor getConstructor() {
+ return constructor;
+ }
+ public void setConstructor(Constructor constructor) {
+ this.constructor = constructor;
+ }
public void closedInvoked() {
+ if (getDelegate()!=null) {
+ try {
+ getDelegate().close();
+ }catch (SQLException ignore) {
+ }
+ }
closed = true;
delegate = null;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // get the name of the method for comparison
- final String name = method.getName();
+ if (compare(TOSTRING_VAL,method)) {
+ return toString();
+ }
// was close invoked?
- boolean close = compare(JdbcInterceptor.CLOSE_VAL, name);
+ boolean close = compare(CLOSE_VAL, method);
// allow close to be called multiple times
if (close && closed)
return null;
// are we calling isClosed?
- if (compare(JdbcInterceptor.ISCLOSED_VAL, name))
+ if (compare(ISCLOSED_VAL, method))
return Boolean.valueOf(closed);
// if we are calling anything else, bail out
if (closed)
throw new SQLException("Statement closed.");
- if (name.equals("getConnection")){
+ if (compare(GETCONNECTION_VAL,method)){
return connection;
}
boolean process = isExecuteQuery(method);
// if we are executing, get the current time
Object result = null;
try {
- // execute the query
- result = method.invoke(delegate, args);
+ // perform close cleanup
+ if (close) {
+ closedInvoked();
+ } else {
+ // execute the query
+ result = method.invoke(delegate, args);
+ }
} catch (Throwable t) {
if (t instanceof InvocationTargetException) {
InvocationTargetException it = (InvocationTargetException) t;
throw t;
}
}
- // perform close cleanup
- if (close) {
- closeInvoked();
- }
if (process){
Constructor<?> cons = getResultSetConstructor();
result = cons.newInstance(new Object[]{new ResultSetProxy(actualProxy, result)});
}
return result;
}
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer(StatementProxy.class.getName());
+ buf.append("[Proxy=");
+ buf.append(System.identityHashCode(this));
+ buf.append("; Sql=");
+ buf.append(getSql());
+ buf.append("; Delegate=");
+ buf.append(getDelegate());
+ buf.append("; Connection=");
+ buf.append(getConnection());
+ buf.append("]");
+ return buf.toString();
+ }
}
protected class ResultSetProxy implements InvocationHandler {
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.jdbc.test;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+
+import org.apache.tomcat.jdbc.pool.interceptor.StatementCache;
+
+public class TestStatementCache extends DefaultTestCase {
+
+
+ public TestStatementCache(String name) {
+ super(name);
+ }
+
+ private static volatile TestStatementCacheInterceptor interceptor = null;
+
+
+ @Override
+ protected void tearDown() throws Exception {
+ // TODO Auto-generated method stub
+ this.interceptor = null;
+ super.tearDown();
+ }
+
+
+
+
+ private void config(boolean cachePrepared, boolean cacheCallable, int max) {
+ datasource.getPoolProperties().setJdbcInterceptors(TestStatementCacheInterceptor.class.getName()+
+ "(prepared="+cachePrepared+",callable="+cacheCallable+",max="+max+")");
+ }
+
+ public void testIsCacheEnabled() throws Exception {
+ init();
+ config(true,true,50);
+ datasource.getConnection().close();
+ assertNotNull("Interceptor was not created.", interceptor);
+ }
+
+ public void testCacheProperties() throws Exception {
+ init();
+ config(true,true,50);
+ datasource.getConnection().close();
+ assertEquals(true, interceptor.isCacheCallable());
+ assertEquals(true, interceptor.isCachePrepared());
+ assertEquals(50,interceptor.getMaxCacheSize());
+ }
+
+ public void testCacheProperties2() throws Exception {
+ init();
+ config(false,false,100);
+ datasource.getConnection().close();
+ assertEquals(false, interceptor.isCacheCallable());
+ assertEquals(false, interceptor.isCachePrepared());
+ assertEquals(100,interceptor.getMaxCacheSize());
+ }
+
+ public void testPreparedStatementCache() throws Exception {
+ init();
+ config(true,false,100);
+ Connection con = datasource.getConnection();
+ PreparedStatement ps1 = con.prepareStatement("select 1");
+ PreparedStatement ps2 = con.prepareStatement("select 1");
+ assertEquals(0,interceptor.getCacheSize().get());
+ ps1.close();
+ assertTrue(ps1.isClosed());
+ assertEquals(1,interceptor.getCacheSize().get());
+ PreparedStatement ps3 = con.prepareStatement("select 1");
+ assertEquals(0,interceptor.getCacheSize().get());
+ ps2.close();
+ assertTrue(ps2.isClosed());
+ ps3.close();
+ assertTrue(ps3.isClosed());
+ assertEquals(1,interceptor.getCacheSize().get());
+ }
+
+ public void testPreparedStatementCache2() throws Exception {
+ init();
+ config(false,false,100);
+ Connection con = datasource.getConnection();
+ PreparedStatement ps1 = con.prepareStatement("select 1");
+ PreparedStatement ps2 = con.prepareStatement("select 1");
+ assertEquals(0,interceptor.getCacheSize().get());
+ ps1.close();
+ assertTrue(ps1.isClosed());
+ assertEquals(0,interceptor.getCacheSize().get());
+ PreparedStatement ps3 = con.prepareStatement("select 1");
+ assertEquals(0,interceptor.getCacheSize().get());
+ ps2.close();
+ assertTrue(ps2.isClosed());
+ ps3.close();
+ assertTrue(ps3.isClosed());
+ assertEquals(0,interceptor.getCacheSize().get());
+ }
+
+ public void testCallableStatementCache() throws Exception {
+ }
+
+ public void testMaxCacheSize() throws Exception {
+ init();
+ config(true,false,100);
+ Connection con1 = datasource.getConnection();
+ Connection con2 = datasource.getConnection();
+ for (int i=0; i<120; i++) {
+ Connection con = (i%2==0)?con1:con2;
+ PreparedStatement ps = con.prepareStatement("select "+i);
+ ps.close();
+ }
+ assertEquals(100,interceptor.getCacheSize().get());
+ }
+
+
+ public static class TestStatementCacheInterceptor extends StatementCache {
+ public TestStatementCacheInterceptor() {
+ TestStatementCache.interceptor = this;
+ }
+ }
+
+}