001: /*
002: * Copyright 2003 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package velosurf.sql;
018:
019: import java.sql.SQLException;
020: import java.sql.ResultSet;
021: import java.sql.Connection;
022:
023: import java.util.Iterator;
024: import java.util.List;
025:
026: import velosurf.util.HashMultiMap;
027: import velosurf.util.Logger;
028:
029: /** This class is a pool of PooledPreparedStatements.
030: *
031: * @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
032: *
033: */
034: public class PreparedStatementPool implements Runnable, Pool {
035:
036: /** build a new pool.
037: *
038: * @param connectionPool connection pool
039: */
040: protected PreparedStatementPool(ConnectionPool connectionPool) {
041: this .connectionPool = connectionPool;
042: checkTimeoutThread = new Thread(this );
043: // checkTimeoutThread.start();
044: }
045:
046: /** get a PooledPreparedStatement associated with this query.
047: *
048: * @param query an SQL query
049: * @exception SQLException thrown by the database engine
050: * @return a valid statement
051: */
052: public synchronized PooledPreparedStatement getPreparedStatement(
053: String query) throws SQLException {
054: Logger.trace("prepare-" + query);
055: PooledPreparedStatement statement = null;
056: ConnectionWrapper connection = null;
057: List available = statementsMap.get(query);
058: for (Iterator it = available.iterator(); it.hasNext();) {
059: statement = (PooledPreparedStatement) it.next();
060: if (statement.isValid()) {
061: if (!statement.isInUse()
062: && !(connection = (ConnectionWrapper) statement
063: .getConnection()).isBusy()) {
064: // check connection
065: if (connection.check()) {
066: statement.notifyInUse();
067: return statement;
068: } else {
069: dropConnection(connection);
070: it.remove();
071: }
072: }
073: } else {
074: it.remove();
075: }
076: }
077: if (count == maxStatements)
078: throw new SQLException(
079: "Error: Too many opened prepared statements!");
080: connection = connectionPool.getConnection();
081: statement = new PooledPreparedStatement(connection, connection
082: .prepareStatement(query,
083: ResultSet.TYPE_SCROLL_INSENSITIVE,
084: ResultSet.CONCUR_READ_ONLY));
085: statementsMap.put(query, statement);
086: statement.notifyInUse();
087: return statement;
088: }
089:
090: /** cycle through statements to check and recycle them.
091: */
092: public void run() {
093: while (running) {
094: try {
095: Thread.sleep(checkDelay);
096: } catch (InterruptedException e) {
097: }
098: long now = System.currentTimeMillis();
099: PooledPreparedStatement statement = null;
100: for (Iterator it = statementsMap.keySet().iterator(); it
101: .hasNext();)
102: for (Iterator jt = statementsMap.get(it.next())
103: .iterator(); jt.hasNext();) {
104: statement = (PooledPreparedStatement) jt.next();
105: if (statement.isInUse()
106: && now - statement.getTagTime() > timeout)
107: statement.notifyOver();
108: }
109: }
110: }
111:
112: /** close all statements.
113: */
114: public void clear() {
115: // close all statements
116: for (Iterator it = statementsMap.keySet().iterator(); it
117: .hasNext();)
118: for (Iterator jt = statementsMap.get(it.next()).iterator(); jt
119: .hasNext();)
120: try {
121: ((PooledPreparedStatement) jt.next()).close();
122: } catch (SQLException e) { // don't care now...
123: Logger.log(e);
124: }
125: statementsMap.clear();
126: }
127:
128: /* drop all statements relative to a specific connection
129: * @param connection the connection
130: */
131: private void dropConnection(Connection connection) {
132: for (Iterator it = statementsMap.keySet().iterator(); it
133: .hasNext();)
134: for (Iterator jt = statementsMap.get(it.next()).iterator(); jt
135: .hasNext();) {
136: PooledPreparedStatement statement = (PooledPreparedStatement) jt
137: .next();
138: try {
139: statement.close();
140: } catch (SQLException sqle) {
141: }
142: statement.setInvalid();
143: }
144: try {
145: connection.close();
146: } catch (SQLException sqle) {
147: }
148: }
149:
150: /** clear statements on exit.
151: */
152: protected void finalize() {
153: clear();
154: }
155:
156: /** debug - get usage statistics.
157: *
158: * @return an int array : [nb of statements in use , total nb of statements]
159: */
160: public int[] getUsageStats() {
161: int[] stats = new int[] { 0, 0 };
162: for (Iterator it = statementsMap.keySet().iterator(); it
163: .hasNext();)
164: for (Iterator jt = statementsMap.get(it.next()).iterator(); jt
165: .hasNext();)
166: if (!((PooledPreparedStatement) jt.next()).isInUse())
167: stats[0]++;
168: stats[1] = statementsMap.size();
169: return stats;
170: }
171:
172: /** connection pool.
173: */
174: private ConnectionPool connectionPool;
175:
176: /** statements count.
177: */
178: private int count = 0;
179: /** map queries -> statements.
180: */
181: private HashMultiMap statementsMap = new HashMultiMap(); // query -> PooledPreparedStatement
182: /** running thread.
183: */
184: private Thread checkTimeoutThread = null;
185: /** true if running.
186: */
187: private boolean running = true;
188:
189: /** check delay.
190: */
191: private static final long checkDelay = 30 * 1000;
192: /** after this timeout, statements are recycled even if not closed.
193: */
194: private static final long timeout = 60 * 60 * 1000;
195: /** max number of statements.
196: */
197: private static final int maxStatements = 50;
198: }
|