2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2016 - 2017 AT&T
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 /* Licensed to the Apache Software Foundation (ASF) under one or more
22 * contributor license agreements. See the NOTICE file distributed with
23 * this work for additional information regarding copyright ownership.
24 * The ASF licenses this file to You under the Apache License, Version 2.0
25 * (the "License"); you may not use this file except in compliance with
26 * the License. You may obtain a copy of the License at
28 * http://www.apache.org/licenses/LICENSE-2.0
30 * Unless required by applicable law or agreed to in writing, software
31 * distributed under the License is distributed on an "AS IS" BASIS,
32 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33 * See the License for the specific language governing permissions and
34 * limitations under the License.
36 package org.apache.tomcat.jdbc.pool.interceptor;
38 import java.lang.reflect.Constructor;
39 import java.lang.reflect.InvocationTargetException;
40 import java.lang.reflect.Method;
41 import java.sql.ResultSet;
42 import java.sql.Statement;
43 import java.util.Arrays;
45 import java.util.concurrent.ConcurrentHashMap;
46 import java.util.concurrent.atomic.AtomicInteger;
48 import org.apache.tomcat.jdbc.pool.ConnectionPool;
49 import org.apache.tomcat.jdbc.pool.PoolProperties.InterceptorProperty;
50 import org.apache.tomcat.jdbc.pool.PooledConnection;
53 * Interceptor that caches {@code PreparedStatement} and/or
54 * {@code CallableStatement} instances on a connection.
56 public class StatementCache extends StatementDecoratorInterceptor {
57 protected static final String[] ALL_TYPES = new String[] {PREPARE_STATEMENT,PREPARE_CALL};
58 protected static final String[] CALLABLE_TYPE = new String[] {PREPARE_CALL};
59 protected static final String[] PREPARED_TYPE = new String[] {PREPARE_STATEMENT};
60 protected static final String[] NO_TYPE = new String[] {};
62 protected static final String STATEMENT_CACHE_ATTR = StatementCache.class.getName() + ".cache";
64 /*begin properties for the statement cache*/
65 private boolean cachePrepared = true;
66 private boolean cacheCallable = false;
67 private int maxCacheSize = 50;
68 private PooledConnection pcon;
69 private String[] types;
72 public boolean isCachePrepared() {
76 public boolean isCacheCallable() {
80 public int getMaxCacheSize() {
84 public String[] getTypes() {
88 public AtomicInteger getCacheSize() {
93 public void setProperties(Map<String, InterceptorProperty> properties) {
94 super.setProperties(properties);
95 InterceptorProperty p = properties.get("prepared");
96 if (p!=null) cachePrepared = p.getValueAsBoolean(cachePrepared);
97 p = properties.get("callable");
98 if (p!=null) cacheCallable = p.getValueAsBoolean(cacheCallable);
99 p = properties.get("max");
100 if (p!=null) maxCacheSize = p.getValueAsInt(maxCacheSize);
101 if (cachePrepared && cacheCallable) {
102 this.types = ALL_TYPES;
103 } else if (cachePrepared) {
104 this.types = PREPARED_TYPE;
105 } else if (cacheCallable) {
106 this.types = CALLABLE_TYPE;
108 this.types = NO_TYPE;
112 /*end properties for the statement cache*/
114 /*begin the cache size*/
115 private static ConcurrentHashMap<ConnectionPool,AtomicInteger> cacheSizeMap =
116 new ConcurrentHashMap<>();
118 private AtomicInteger cacheSize;
121 public void poolStarted(ConnectionPool pool) {
122 cacheSizeMap.putIfAbsent(pool, new AtomicInteger(0));
123 super.poolStarted(pool);
127 public void poolClosed(ConnectionPool pool) {
128 cacheSizeMap.remove(pool);
129 super.poolClosed(pool);
131 /*end the cache size*/
133 /*begin the actual statement cache*/
135 public void reset(ConnectionPool parent, PooledConnection con) {
136 super.reset(parent, con);
141 cacheSize = cacheSizeMap.get(parent);
143 if (!pcon.getAttributes().containsKey(STATEMENT_CACHE_ATTR)) {
144 ConcurrentHashMap<CacheKey,CachedStatement> cache =
145 new ConcurrentHashMap<>();
146 pcon.getAttributes().put(STATEMENT_CACHE_ATTR,cache);
152 public void disconnected(ConnectionPool parent, PooledConnection con, boolean finalizing) {
153 @SuppressWarnings("unchecked")
154 ConcurrentHashMap<CacheKey,CachedStatement> statements =
155 (ConcurrentHashMap<CacheKey,CachedStatement>)con.getAttributes().get(STATEMENT_CACHE_ATTR);
157 if (statements!=null) {
158 for (Map.Entry<CacheKey, CachedStatement> p : statements.entrySet()) {
159 closeStatement(p.getValue());
164 super.disconnected(parent, con, finalizing);
167 public void closeStatement(CachedStatement st) {
168 if (st==null) return;
173 protected Object createDecorator(Object proxy, Method method, Object[] args,
174 Object statement, Constructor<?> constructor, String sql)
175 throws InstantiationException, IllegalAccessException, InvocationTargetException {
176 boolean process = process(this.types, method, false);
178 Object result = null;
179 CachedStatement statementProxy = new CachedStatement((Statement)statement,sql);
180 result = constructor.newInstance(new Object[] { statementProxy });
181 statementProxy.setActualProxy(result);
182 statementProxy.setConnection(proxy);
183 statementProxy.setConstructor(constructor);
184 statementProxy.setCacheKey(createCacheKey(method, args));
187 return super.createDecorator(proxy, method, args, statement, constructor, sql);
192 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
193 boolean process = process(this.types, method, false);
194 if (process && args.length>0 && args[0] instanceof String) {
195 CachedStatement statement = isCached(method, args);
196 if (statement!=null) {
197 //remove it from the cache since it is used
198 removeStatement(statement);
199 return statement.getActualProxy();
201 return super.invoke(proxy, method, args);
204 return super.invoke(proxy,method,args);
208 public CachedStatement isCached(Method method, Object[] args) {
209 @SuppressWarnings("unchecked")
210 ConcurrentHashMap<CacheKey,CachedStatement> cache =
211 (ConcurrentHashMap<CacheKey,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
212 return cache.get(createCacheKey(method, args));
215 public boolean cacheStatement(CachedStatement proxy) {
216 @SuppressWarnings("unchecked")
217 ConcurrentHashMap<CacheKey,CachedStatement> cache =
218 (ConcurrentHashMap<CacheKey,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
219 if (proxy.getCacheKey()==null) {
221 } else if (cache.containsKey(proxy.getCacheKey())) {
223 } else if (cacheSize.get()>=maxCacheSize) {
225 } else if (cacheSize.incrementAndGet()>maxCacheSize) {
226 cacheSize.decrementAndGet();
229 //cache the statement
230 cache.put(proxy.getCacheKey(), proxy);
235 public boolean removeStatement(CachedStatement proxy) {
236 @SuppressWarnings("unchecked")
237 ConcurrentHashMap<CacheKey,CachedStatement> cache =
238 (ConcurrentHashMap<CacheKey,CachedStatement>)pcon.getAttributes().get(STATEMENT_CACHE_ATTR);
239 if (cache.remove(proxy.getCacheKey()) != null) {
240 cacheSize.decrementAndGet();
246 /*end the actual statement cache*/
249 protected class CachedStatement extends StatementDecoratorInterceptor.StatementProxy<Statement> {
250 boolean cached = false;
252 public CachedStatement(Statement parent, String sql) {
257 public void closeInvoked() {
259 boolean shouldClose = true;
260 if (cacheSize.get() < maxCacheSize) {
261 //cache a proxy so that we don't reuse the facade
262 CachedStatement proxy = new CachedStatement(getDelegate(),getSql());
263 proxy.setCacheKey(getCacheKey());
266 ResultSet result = getDelegate().getResultSet();
267 if (result != null && !result.isClosed()) {
270 //create a new facade
271 Object actualProxy = getConstructor().newInstance(new Object[] { proxy });
272 proxy.setActualProxy(actualProxy);
273 proxy.setConnection(getConnection());
274 proxy.setConstructor(getConstructor());
275 if (cacheStatement(proxy)) {
279 } catch (Exception x) {
280 removeStatement(proxy);
284 super.closeInvoked();
291 public void forceClose() {
292 removeStatement(this);
293 super.closeInvoked();
296 public CacheKey getCacheKey() {
300 public void setCacheKey(CacheKey cacheKey) {
306 protected CacheKey createCacheKey(Method method, Object[] args) {
307 return createCacheKey(method.getName(), args);
310 protected CacheKey createCacheKey(String methodName, Object[] args) {
312 if (compare(PREPARE_STATEMENT, methodName)) {
313 key = new CacheKey(PREPARE_STATEMENT, args);
314 } else if (compare(PREPARE_CALL, methodName)) {
315 key = new CacheKey(PREPARE_CALL, args);
321 private static final class CacheKey {
322 private final String stmtType;
323 private final Object[] args;
324 private CacheKey(String type, Object[] methodArgs) {
330 public int hashCode() {
331 final int prime = 31;
333 result = prime * result + Arrays.hashCode(args);
334 result = prime * result
335 + ((stmtType == null) ? 0 : stmtType.hashCode());
340 public boolean equals(Object obj) {
345 if (getClass() != obj.getClass())
347 CacheKey other = (CacheKey) obj;
348 if (!Arrays.equals(args, other.args))
350 if (stmtType == null) {
351 if (other.stmtType != null)
353 } else if (!stmtType.equals(other.stmtType))