[SDNC-5] Rebase sdnc-core
[sdnc/core.git] / dblib / common / src / main / java / org / apache / tomcat / jdbc / pool / interceptor / StatementCache.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * openecomp
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
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
27  *
28  *      http://www.apache.org/licenses/LICENSE-2.0
29  *
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.
35  */
36 package org.apache.tomcat.jdbc.pool.interceptor;
37
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;
44 import java.util.Map;
45 import java.util.concurrent.ConcurrentHashMap;
46 import java.util.concurrent.atomic.AtomicInteger;
47
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;
51
52 /**
53  * Interceptor that caches {@code PreparedStatement} and/or
54  * {@code CallableStatement} instances on a connection.
55  */
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[] {};
61
62     protected static final String STATEMENT_CACHE_ATTR = StatementCache.class.getName() + ".cache";
63
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;
70
71
72     public boolean isCachePrepared() {
73         return cachePrepared;
74     }
75
76     public boolean isCacheCallable() {
77         return cacheCallable;
78     }
79
80     public int getMaxCacheSize() {
81         return maxCacheSize;
82     }
83
84     public String[] getTypes() {
85         return types;
86     }
87
88     public AtomicInteger getCacheSize() {
89         return cacheSize;
90     }
91
92     @Override
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;
107         } else {
108             this.types = NO_TYPE;
109         }
110
111     }
112     /*end properties for the statement cache*/
113
114     /*begin the cache size*/
115     private static ConcurrentHashMap<ConnectionPool,AtomicInteger> cacheSizeMap =
116         new ConcurrentHashMap<>();
117
118     private AtomicInteger cacheSize;
119
120     @Override
121     public void poolStarted(ConnectionPool pool) {
122         cacheSizeMap.putIfAbsent(pool, new AtomicInteger(0));
123         super.poolStarted(pool);
124     }
125
126     @Override
127     public void poolClosed(ConnectionPool pool) {
128         cacheSizeMap.remove(pool);
129         super.poolClosed(pool);
130     }
131     /*end the cache size*/
132
133     /*begin the actual statement cache*/
134     @Override
135     public void reset(ConnectionPool parent, PooledConnection con) {
136         super.reset(parent, con);
137         if (parent==null) {
138             cacheSize = null;
139             this.pcon = null;
140         } else {
141             cacheSize = cacheSizeMap.get(parent);
142             this.pcon = con;
143             if (!pcon.getAttributes().containsKey(STATEMENT_CACHE_ATTR)) {
144                 ConcurrentHashMap<CacheKey,CachedStatement> cache =
145                         new ConcurrentHashMap<>();
146                 pcon.getAttributes().put(STATEMENT_CACHE_ATTR,cache);
147             }
148         }
149     }
150
151     @Override
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);
156
157         if (statements!=null) {
158             for (Map.Entry<CacheKey, CachedStatement> p : statements.entrySet()) {
159                 closeStatement(p.getValue());
160             }
161             statements.clear();
162         }
163
164         super.disconnected(parent, con, finalizing);
165     }
166
167     public void closeStatement(CachedStatement st) {
168         if (st==null) return;
169         st.forceClose();
170     }
171
172     @Override
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);
177         if (process) {
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));
185             return result;
186         } else {
187             return super.createDecorator(proxy, method, args, statement, constructor, sql);
188         }
189     }
190
191     @Override
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();
200             } else {
201                 return super.invoke(proxy, method, args);
202             }
203         } else {
204             return super.invoke(proxy,method,args);
205         }
206     }
207
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));
213     }
214
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) {
220             return false;
221         } else if (cache.containsKey(proxy.getCacheKey())) {
222             return false;
223         } else if (cacheSize.get()>=maxCacheSize) {
224             return false;
225         } else if (cacheSize.incrementAndGet()>maxCacheSize) {
226             cacheSize.decrementAndGet();
227             return false;
228         } else {
229             //cache the statement
230             cache.put(proxy.getCacheKey(), proxy);
231             return true;
232         }
233     }
234
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();
241             return true;
242         } else {
243             return false;
244         }
245     }
246     /*end the actual statement cache*/
247
248
249     protected class CachedStatement extends StatementDecoratorInterceptor.StatementProxy<Statement> {
250         boolean cached = false;
251         CacheKey key;
252         public CachedStatement(Statement parent, String sql) {
253             super(parent, sql);
254         }
255
256         @Override
257         public void closeInvoked() {
258             //should we cache it
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());
264                 try {
265                     // clear Resultset
266                     ResultSet result = getDelegate().getResultSet();
267                     if (result != null && !result.isClosed()) {
268                         result.close();
269                     }
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)) {
276                         proxy.cached = true;
277                         shouldClose = false;
278                     }
279                 } catch (Exception x) {
280                     removeStatement(proxy);
281                 }
282             }
283             if (shouldClose) {
284                 super.closeInvoked();
285             }
286             closed = true;
287             delegate = null;
288
289         }
290
291         public void forceClose() {
292             removeStatement(this);
293             super.closeInvoked();
294         }
295
296         public CacheKey getCacheKey() {
297             return key;
298         }
299
300         public void setCacheKey(CacheKey cacheKey) {
301             key = cacheKey;
302         }
303
304     }
305
306     protected CacheKey createCacheKey(Method method, Object[] args) {
307         return createCacheKey(method.getName(), args);
308     }
309
310     protected CacheKey createCacheKey(String methodName, Object[] args) {
311         CacheKey key = null;
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);
316         }
317         return key;
318     }
319
320
321     private static final class CacheKey {
322         private final String stmtType;
323         private final Object[] args;
324         private CacheKey(String type, Object[] methodArgs) {
325             stmtType = type;
326             args = methodArgs;
327         }
328
329         @Override
330         public int hashCode() {
331             final int prime = 31;
332             int result = 1;
333             result = prime * result + Arrays.hashCode(args);
334             result = prime * result
335                     + ((stmtType == null) ? 0 : stmtType.hashCode());
336             return result;
337         }
338
339         @Override
340         public boolean equals(Object obj) {
341             if (this == obj)
342                 return true;
343             if (obj == null)
344                 return false;
345             if (getClass() != obj.getClass())
346                 return false;
347             CacheKey other = (CacheKey) obj;
348             if (!Arrays.equals(args, other.args))
349                 return false;
350             if (stmtType == null) {
351                 if (other.stmtType != null)
352                     return false;
353             } else if (!stmtType.equals(other.stmtType))
354                 return false;
355             return true;
356         }
357     }
358 }