001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.jdbc.conn;
012:
013: import java.sql.SQLException;
014: import java.util.Iterator;
015: import java.util.HashMap;
016: import java.util.Map;
017:
018: import com.versant.core.common.Debug;
019: import com.versant.core.common.BindingSupportImpl;
020:
021: /**
022: * Count limited LRU PreparedStatement pool for LoggingConnection.
023: */
024: public final class PreparedStatementPool {
025:
026: private final LoggingConnection con;
027: private final HashMap keys = new HashMap(); // maps Key -> Key
028: private Key head; // most recently accessed Key
029: private Key tail; // least recently accessed Key
030: private int max;
031: private int numActive;
032: private int numIdle;
033:
034: public PreparedStatementPool(LoggingConnection con, int max) {
035: this .con = con;
036: this .max = max;
037: }
038:
039: public LoggingConnection getCon() {
040: return con;
041: }
042:
043: public int getMax() {
044: return max;
045: }
046:
047: /**
048: * Get the total number of PS currently idle in the pool.
049: */
050: public int getNumIdle() {
051: return numIdle;
052: }
053:
054: /**
055: * Get the number of active PS outside of the pool.
056: */
057: public int getNumActive() {
058: return numActive;
059: }
060:
061: /**
062: * Get the total number of PS (numActive + numIdle).
063: */
064: public int getSize() {
065: return numIdle + numActive;
066: }
067:
068: /**
069: * Borrow a PS from the pool. This will create one if none are available.
070: * If this pushes the total number of PSes over max then idle PS'es are
071: * closed as needed.
072: */
073: public PooledPreparedStatement borrowPS(Key pkey)
074: throws SQLException {
075: Key key = (Key) keys.get(pkey);
076: if (key == null) {
077: keys.put(key = pkey, pkey);
078: } else {
079: removeFromLRUList(key);
080: }
081: addToHeadOfLRUList(key);
082: PooledPreparedStatement ps = key.removePsFromList();
083: if (ps == null) {
084: ps = con.prepareStatementImp(key.getSql(), key
085: .getResultSetType(), key.getResultSetConcurrency(),
086: key);
087: } else {
088: --numIdle;
089: }
090: ++key.activeCount;
091: ++numActive;
092: if (max > 0) {
093: closeExcessStatements();
094: }
095: if (Debug.DEBUG) {
096: check();
097: }
098: return ps;
099: }
100:
101: /**
102: * Return a PS to the pool.
103: */
104: public void returnPS(PooledPreparedStatement ps) {
105: Key key = ps.getKey();
106: key.addPsToList(ps);
107: --key.activeCount;
108: ++numIdle;
109: --numActive;
110: if (max > 0) {
111: closeExcessStatements();
112: }
113: if (Debug.DEBUG) {
114: check();
115: }
116: }
117:
118: private void closeExcessStatements() {
119: int toClose = (numActive + numIdle) - max;
120: if (toClose > numIdle) {
121: toClose = numIdle;
122: }
123: for (Key key = tail; toClose > 0;) {
124: for (;;) {
125: PooledPreparedStatement ps = key.removePsFromList();
126: if (ps == null) {
127: break;
128: }
129: try {
130: ps.closeRealStatement();
131: } catch (SQLException e) {
132: // ignore
133: }
134: --numIdle;
135: if (--toClose == 0) {
136: break;
137: }
138: }
139: if (key.psList == null) {
140: if (key.activeCount == 0) {
141: Key next = key.next;
142: removeFromLRUList(key);
143: keys.remove(key);
144: key = next;
145: } else {
146: key = key.next;
147: }
148: }
149: }
150: if (Debug.DEBUG) {
151: check();
152: }
153: }
154:
155: private void check() {
156: try {
157: // count the number of idle PS'es
158: int c = 0;
159: for (Iterator i = keys.keySet().iterator(); i.hasNext();) {
160: Key key = (Key) i.next();
161: for (PooledPreparedStatement ps = key.psList; ps != null; ++c) {
162: if (ps == ps.next) {
163: throw BindingSupportImpl.getInstance()
164: .internal(
165: "ps == ps.next for key " + key);
166: }
167: ps = ps.next;
168: }
169: }
170: if (numIdle != c) {
171: throw BindingSupportImpl.getInstance().internal(
172: "numIdle is " + numIdle + " but there are " + c
173: + " idle PS'es in map");
174: }
175: // walk the LRU list checking the links, detected dups and making
176: // sure all entries are in the map and that the map has no extra
177: // entries
178: c = 0;
179: int totActive = 0;
180: Map dupMap = new HashMap();
181: for (Key key = tail; key != null; key = key.next, ++c) {
182: if (key.next != null) {
183: if (key.next.prev != key) {
184: throw BindingSupportImpl.getInstance()
185: .internal("key.next.prev != key");
186: }
187: }
188: Key pkey = (Key) dupMap.get(key);
189: if (pkey != null) {
190: throw BindingSupportImpl.getInstance().internal(
191: "Dup key in LRU list: " + pkey);
192: }
193: dupMap.put(key, key);
194: if (!keys.containsKey(key)) {
195: throw BindingSupportImpl.getInstance().internal(
196: "Key in LRU list not in map: " + key);
197: }
198: totActive += key.activeCount;
199: }
200: if (c != keys.size()) {
201: throw BindingSupportImpl.getInstance().internal(
202: "There are " + c + " keys in the LRU list and "
203: + keys.size() + " in the keys map");
204: }
205: if (numActive != totActive) {
206: throw BindingSupportImpl.getInstance().internal(
207: "numActive is " + numIdle + " but there are "
208: + c + " active PS'es in keys");
209: }
210: } catch (RuntimeException e) {
211: System.out.println("PreparedStatementPool check failed: "
212: + e);
213: dump();
214: e.printStackTrace(System.out);
215: throw e;
216: }
217: }
218:
219: /**
220: * Remove key from the double linked LRU list.
221: */
222: private void removeFromLRUList(Key key) {
223: if (key.prev != null) {
224: key.prev.next = key.next;
225: } else {
226: tail = key.next;
227: }
228: if (key.next != null) {
229: key.next.prev = key.prev;
230: } else {
231: head = key.prev;
232: }
233: }
234:
235: /**
236: * Add key to the head of the double linked LRU list. This will make it
237: * the most recently accessed object.
238: */
239: private void addToHeadOfLRUList(Key key) {
240: key.next = null;
241: key.prev = head;
242: if (head != null)
243: head.next = key;
244: head = key;
245: if (tail == null)
246: tail = key;
247: }
248:
249: /**
250: * Dump the contents of the pool.
251: */
252: public void dump() {
253: if (Debug.DEBUG) {
254: System.out.println("=== keys ===");
255: for (Iterator i = keys.keySet().iterator(); i.hasNext();) {
256: Key key = (Key) i.next();
257: System.out.println(key.toDumpString());
258: }
259: System.out.println("=== LRU list ===");
260: for (Key key = tail; key != null; key = key.next) {
261: System.out.println(key);
262: }
263: System.out.println("---");
264: }
265: }
266:
267: /**
268: * This is the key for this pool. These are linked together when in the
269: * pool to form a double linked list used for LRU evictions. Each
270: * maintains a single linked list of PooledPreparedStatement's.
271: */
272: public static final class Key {
273:
274: private final String sql;
275: private final int resultSetType;
276: private final int resultSetConcurrency;
277:
278: private PooledPreparedStatement psList;
279: private int activeCount;
280: private Key prev;
281: private Key next;
282:
283: public Key(String sql, int resultSetType,
284: int resultSetConcurrency) {
285: if (sql == null) {
286: throw new NullPointerException("sql is null");
287: }
288: this .sql = sql;
289: this .resultSetType = resultSetType;
290: this .resultSetConcurrency = resultSetConcurrency;
291: }
292:
293: public Key(String sql) {
294: if (sql == null) {
295: throw new NullPointerException("sql is null");
296: }
297: this .sql = sql;
298: resultSetType = 0;
299: resultSetConcurrency = 0;
300: }
301:
302: public String getSql() {
303: return sql;
304: }
305:
306: public int getResultSetType() {
307: return resultSetType;
308: }
309:
310: public int getResultSetConcurrency() {
311: return resultSetConcurrency;
312: }
313:
314: public boolean equals(Object o) {
315: if (!(o instanceof Key))
316: return false;
317: final Key psKey = (Key) o;
318: return resultSetConcurrency == psKey.resultSetConcurrency
319: && resultSetType == psKey.resultSetType
320: && sql.equals(psKey.sql);
321: }
322:
323: public int hashCode() {
324: return sql.hashCode() + resultSetType
325: + resultSetConcurrency;
326: }
327:
328: public String toString() {
329: StringBuffer s = new StringBuffer();
330: s.append(Integer.toHexString(sql.hashCode()));
331: for (int i = 9 - s.length(); i > 0; i--) {
332: s.append(' ');
333: }
334: if (sql.length() > 40) {
335: s.append(sql.substring(0, 37));
336: s.append("...");
337: } else {
338: s.append(sql);
339: }
340: return s.toString();
341: }
342:
343: public String toDumpString() {
344: StringBuffer s = new StringBuffer();
345: s.append(toString());
346: for (PooledPreparedStatement ps = psList; ps != null;) {
347: s.append(" -> ");
348: s.append(ps);
349: if (ps == ps.next) {
350: s.append("*** LOOP ps == ps.next ***");
351: break;
352: }
353: ps = ps.next;
354: }
355: return s.toString();
356: }
357:
358: /**
359: * Do we have no PS'es?
360: */
361: public boolean isPsListEmpty() {
362: return psList == null;
363: }
364:
365: /**
366: * Remove the first PS from our list and return it or null if none
367: * are available.
368: */
369: public PooledPreparedStatement removePsFromList() {
370: if (psList == null) {
371: return null;
372: }
373: PooledPreparedStatement ps = psList;
374: psList = ps.next;
375: ps.next = null;
376: return ps;
377: }
378:
379: /**
380: * Add ps to our list.
381: */
382: public void addPsToList(PooledPreparedStatement ps) {
383: ps.next = psList;
384: psList = ps;
385: }
386: }
387:
388: }
|