aboutsummaryrefslogtreecommitdiffstats
path: root/dblib/common/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java
diff options
context:
space:
mode:
Diffstat (limited to 'dblib/common/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java')
-rw-r--r--dblib/common/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java358
1 files changed, 358 insertions, 0 deletions
diff --git a/dblib/common/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java b/dblib/common/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java
new file mode 100644
index 0000000..e98d3b6
--- /dev/null
+++ b/dblib/common/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java
@@ -0,0 +1,358 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * openecomp
+ * ================================================================================
+ * Copyright (C) 2016 - 2017 AT&T
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+/* 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.pool.interceptor;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.tomcat.jdbc.pool.ConnectionPool;
+import org.apache.tomcat.jdbc.pool.PoolProperties.InterceptorProperty;
+import org.apache.tomcat.jdbc.pool.PooledConnection;
+
+/**
+ * Interceptor that caches {@code PreparedStatement} and/or
+ * {@code CallableStatement} instances on a connection.
+ */
+public class StatementCache extends StatementDecoratorInterceptor {
+ protected static final String[] ALL_TYPES = new String[] {PREPARE_STATEMENT,PREPARE_CALL};
+ 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 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;
+ }
+
+ @Override
+ public void setProperties(Map<String, InterceptorProperty> properties) {
+ super.setProperties(properties);
+ InterceptorProperty p = properties.get("prepared");
+ if (p!=null) cachePrepared = p.getValueAsBoolean(cachePrepared);
+ p = properties.get("callable");
+ if (p!=null) cacheCallable = p.getValueAsBoolean(cacheCallable);
+ p = properties.get("max");
+ if (p!=null) maxCacheSize = p.getValueAsInt(maxCacheSize);
+ if (cachePrepared && cacheCallable) {
+ this.types = ALL_TYPES;
+ } else if (cachePrepared) {
+ this.types = PREPARED_TYPE;
+ } else if (cacheCallable) {
+ this.types = CALLABLE_TYPE;
+ } else {
+ this.types = NO_TYPE;
+ }
+
+ }
+ /*end properties for the statement cache*/
+
+ /*begin the cache size*/
+ private static ConcurrentHashMap<ConnectionPool,AtomicInteger> cacheSizeMap =
+ new ConcurrentHashMap<>();
+
+ private AtomicInteger cacheSize;
+
+ @Override
+ public void poolStarted(ConnectionPool pool) {
+ cacheSizeMap.putIfAbsent(pool, new AtomicInteger(0));
+ super.poolStarted(pool);
+ }
+
+ @Override
+ public void poolClosed(ConnectionPool pool) {
+ cacheSizeMap.remove(pool);
+ super.poolClosed(pool);
+ }
+ /*end the cache size*/
+
+ /*begin the actual statement cache*/
+ @Override
+ public void reset(ConnectionPool parent, PooledConnection con) {
+ super.reset(parent, con);
+ if (parent==null) {
+ cacheSize = null;
+ this.pcon = null;
+ } else {
+ cacheSize = cacheSizeMap.get(parent);
+ this.pcon = con;
+ if (!pcon.getAttributes().containsKey(STATEMENT_CACHE_ATTR)) {
+ ConcurrentHashMap<CacheKey,CachedStatement> cache =
+ new ConcurrentHashMap<>();
+ pcon.getAttributes().put(STATEMENT_CACHE_ATTR,cache);
+ }
+ }
+ }
+
+ @Override
+ public void disconnected(ConnectionPool parent, PooledConnection con, boolean finalizing) {
+ @SuppressWarnings("unchecked")
+ ConcurrentHashMap<CacheKey,CachedStatement> statements =
+ (ConcurrentHashMap<CacheKey,CachedStatement>)con.getAttributes().get(STATEMENT_CACHE_ATTR);
+
+ if (statements!=null) {
+ for (Map.Entry<CacheKey, CachedStatement> p : statements.entrySet()) {
+ closeStatement(p.getValue());
+ }
+ statements.clear();
+ }
+
+ super.disconnected(parent, con, finalizing);
+ }
+
+ public void closeStatement(CachedStatement st) {
+ if (st==null) return;
+ st.forceClose();
+ }
+
+ @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);
+ statementProxy.setCacheKey(createCacheKey(method, args));
+ return result;
+ } else {
+ return super.createDecorator(proxy, method, args, statement, constructor, sql);
+ }
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ boolean process = process(this.types, method, false);
+ if (process && args.length>0 && args[0] instanceof String) {
+ CachedStatement statement = isCached(method, args);
+ 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 CachedStatement isCached(Method method, Object[] args) {
+ @SuppressWarnings("unchecked")
+ ConcurrentHashMap<CacheKey,CachedStatement> cache =
+ (ConcurrentHashMap<CacheKey,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
+ return cache.get(createCacheKey(method, args));
+ }
+
+ public boolean cacheStatement(CachedStatement proxy) {
+ @SuppressWarnings("unchecked")
+ ConcurrentHashMap<CacheKey,CachedStatement> cache =
+ (ConcurrentHashMap<CacheKey,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
+ if (proxy.getCacheKey()==null) {
+ return false;
+ } else if (cache.containsKey(proxy.getCacheKey())) {
+ return false;
+ } else if (cacheSize.get()>=maxCacheSize) {
+ return false;
+ } else if (cacheSize.incrementAndGet()>maxCacheSize) {
+ cacheSize.decrementAndGet();
+ return false;
+ } else {
+ //cache the statement
+ cache.put(proxy.getCacheKey(), proxy);
+ return true;
+ }
+ }
+
+ public boolean removeStatement(CachedStatement proxy) {
+ @SuppressWarnings("unchecked")
+ ConcurrentHashMap<CacheKey,CachedStatement> cache =
+ (ConcurrentHashMap<CacheKey,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
+ if (cache.remove(proxy.getCacheKey()) != null) {
+ cacheSize.decrementAndGet();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ /*end the actual statement cache*/
+
+
+ protected class CachedStatement extends StatementDecoratorInterceptor.StatementProxy<Statement> {
+ boolean cached = false;
+ CacheKey key;
+ public CachedStatement(Statement parent, String sql) {
+ super(parent, sql);
+ }
+
+ @Override
+ public void closeInvoked() {
+ //should we cache it
+ boolean shouldClose = true;
+ if (cacheSize.get() < maxCacheSize) {
+ //cache a proxy so that we don't reuse the facade
+ CachedStatement proxy = new CachedStatement(getDelegate(),getSql());
+ proxy.setCacheKey(getCacheKey());
+ try {
+ // clear Resultset
+ ResultSet result = getDelegate().getResultSet();
+ if (result != null && !result.isClosed()) {
+ result.close();
+ }
+ //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);
+ }
+ }
+ if (shouldClose) {
+ super.closeInvoked();
+ }
+ closed = true;
+ delegate = null;
+
+ }
+
+ public void forceClose() {
+ removeStatement(this);
+ super.closeInvoked();
+ }
+
+ public CacheKey getCacheKey() {
+ return key;
+ }
+
+ public void setCacheKey(CacheKey cacheKey) {
+ key = cacheKey;
+ }
+
+ }
+
+ protected CacheKey createCacheKey(Method method, Object[] args) {
+ return createCacheKey(method.getName(), args);
+ }
+
+ protected CacheKey createCacheKey(String methodName, Object[] args) {
+ CacheKey key = null;
+ if (compare(PREPARE_STATEMENT, methodName)) {
+ key = new CacheKey(PREPARE_STATEMENT, args);
+ } else if (compare(PREPARE_CALL, methodName)) {
+ key = new CacheKey(PREPARE_CALL, args);
+ }
+ return key;
+ }
+
+
+ private static final class CacheKey {
+ private final String stmtType;
+ private final Object[] args;
+ private CacheKey(String type, Object[] methodArgs) {
+ stmtType = type;
+ args = methodArgs;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(args);
+ result = prime * result
+ + ((stmtType == null) ? 0 : stmtType.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ CacheKey other = (CacheKey) obj;
+ if (!Arrays.equals(args, other.args))
+ return false;
+ if (stmtType == null) {
+ if (other.stmtType != null)
+ return false;
+ } else if (!stmtType.equals(other.stmtType))
+ return false;
+ return true;
+ }
+ }
+}