001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: package org.jaffa.persistence.engines.jdbcengine.datasource;
051:
052: import org.apache.log4j.Logger;
053: import java.util.*;
054: import java.io.IOException;
055: import java.sql.Connection;
056: import java.sql.Statement;
057: import java.sql.PreparedStatement;
058: import java.sql.CallableStatement;
059: import java.sql.ResultSet;
060: import java.sql.SQLException;
061: import org.jaffa.persistence.Criteria;
062: import org.jaffa.persistence.engines.jdbcengine.configservice.ClassMetaData;
063: import org.jaffa.persistence.exceptions.PostLoadFailedException;
064: import org.jaffa.persistence.engines.jdbcengine.datasource.exceptions.DataSourceCursorRuntimeException;
065:
066: /** Encapsulates the connection to a data storage mechanism */
067: public class DataSource {
068: private static final Logger log = Logger
069: .getLogger(DataSource.class);
070: private static final String PREPARED_STATEMENT_CACHE_KEY = "PreparedStatementCacheKey";
071: private static final String CALLABLE_STATEMENT_CACHE_KEY = "CallableStatementCacheKey";
072:
073: /** Our actual connection to the database. This connection will be 'close'd on a close().*/
074: private Connection m_connection;
075:
076: /** The size of the hitlist.*/
077: private Integer m_hitlistSize;
078:
079: /** The engine type as defined in init.xml*/
080: private String m_engineType;
081:
082: private Boolean m_usePreparedStatement;
083:
084: /** This will cache the PreparedStatement objects, created against this connection.*/
085: private Map m_preparedStatementCache = null;
086:
087: /** This will cache the CallableStatement objects, created against this connection.*/
088: private Map m_callableStatementCache = null;
089:
090: /** This will hold a collection of Statement objects, created against this connection.
091: * They will all be 'close'd, on a commit()/rollback(), and the collection cleared.
092: * The Statement object can also be closed manually by calling the closeStatement() method.
093: * Its a good idea to do so, when the ResultSet has been completely read.
094: */
095: private Collection m_statements = null;
096:
097: // **** PACKAGE-ACCESS METHODS ****
098:
099: /** Creates a new data source.
100: * The cache for PreparedStatement and CallableStatement objects is maintained outside of this class, since we are going to pool only the connections & not the datasource itself.
101: * If the cache is not supplied initially, then the cache will be maintained for the lifecycle of a datasource only, as opposed to the pooled-connection.
102: * @param connection The connection to the database.
103: * @param hitlistSize The size of the hitlist.
104: * @param engineType The engine type as defined in init.xml
105: * @param usePreparedStatement Decides if PreparedStatements are to be used for Inserts, Updates, Deletes, Locks. This property should be set to 'false' if the primary key of a table has 'CHAR' field(s).
106: * @param statementCache the cache used by the connection for all the PreparedStatement and CallableStatement objects.
107: */
108: DataSource(Connection connection, Integer hitlistSize,
109: String engineType, Boolean usePreparedStatement,
110: Map statementCache) {
111: m_connection = connection;
112: m_hitlistSize = hitlistSize;
113: m_engineType = engineType;
114: m_usePreparedStatement = usePreparedStatement;
115: if (statementCache != null) {
116: if (log.isDebugEnabled())
117: log
118: .debug("This DataSource uses the pooled Connection's statement cache");
119: m_preparedStatementCache = (Map) statementCache
120: .get(PREPARED_STATEMENT_CACHE_KEY);
121: if (m_preparedStatementCache == null) {
122: m_preparedStatementCache = new WeakHashMap();
123: statementCache.put(PREPARED_STATEMENT_CACHE_KEY,
124: m_preparedStatementCache);
125: }
126: m_callableStatementCache = (Map) statementCache
127: .get(CALLABLE_STATEMENT_CACHE_KEY);
128: if (m_callableStatementCache == null) {
129: m_callableStatementCache = new WeakHashMap();
130: statementCache.put(CALLABLE_STATEMENT_CACHE_KEY,
131: m_callableStatementCache);
132: }
133: } else {
134: if (log.isDebugEnabled())
135: log
136: .debug("This DataSource will cache statements only for the lifetime of the DataSource");
137: m_preparedStatementCache = new WeakHashMap();
138: m_callableStatementCache = new WeakHashMap();
139: }
140: m_statements = new ArrayList();
141: }
142:
143: /** Returns the Connection object, used for creating the DataSource.
144: * @return a Connection.
145: */
146: Connection getConnection() {
147: return m_connection;
148: }
149:
150: /** Closes the statement and removes it from the internal collection.
151: * @param statement the Statement object to be closed.
152: * @throws SQLException if any database error occurs.
153: */
154: public void closeStatement(Statement statement) throws SQLException {
155: if (log.isDebugEnabled())
156: log
157: .debug("Closing the Statement and removing it from the internal collection.");
158: statement.close();
159: m_statements.remove(statement);
160: }
161:
162: /** Returns a Statement object. This object will be added to an internal collection and will be closed when the DataSource is closed.
163: * It is a good idea to invoke the closeStatement() method when done with the statement.
164: * @throws SQLException if any database error occurs.
165: * @return the Statement object.
166: */
167: public Statement getStatement() throws SQLException {
168: Statement statement = m_connection.createStatement();
169: m_statements.add(statement);
170: return statement;
171: }
172:
173: // **** PUBLIC METHODS ****
174:
175: /** Executes a query against the underlying data source. Returns a Collection of persistent objects.
176: * The Statement object that is internally used for executing the query, will be automatically closed, once the recordset has been completely traversed.
177: * @param sql The query to execute.
178: * @param classMetaData The ClassMetaData defintion to be used for molding the ResultSet into Persistent objects.
179: * @param criteria The Criteria used for the query. This will provide the values to set the various flags on the Persistent object.
180: * @param queryTimeout This will be used for setting the timeout value on the Statement object; zero means there is no limit.
181: * @throws SQLException if any database error occurs.
182: * @throws PostLoadFailedException if any error is thrown in the PostLoad trigger of the persistent object.
183: * @throws DataSourceCursorRuntimeException if any error occurs while molding the row into the Persistent object.
184: * @throws IOException if any error occurs in reading the data from the database.
185: * @return a Collection of persistent objects.
186: */
187: public Collection executeQuery(String sql,
188: ClassMetaData classMetaData, Criteria criteria,
189: int queryTimeout) throws SQLException,
190: PostLoadFailedException, DataSourceCursorRuntimeException,
191: IOException {
192: Statement statement = getStatement();
193: // The following sets the timeout; zero means there is no limit
194: // The setting works in MS-Sql-Server only !!
195: statement.setQueryTimeout(queryTimeout);
196: ResultSet resultSet = statement.executeQuery(sql);
197: return new DataSourceCursor(this , resultSet, classMetaData,
198: criteria);
199: }
200:
201: /** Executes the sql, which should be an update/insert/delete statement.
202: * The Statement object that is internally used for executing the update, will be immediately closed.
203: * @param sql the sql to execute.
204: * @throws SQLException if any database error occurs.
205: */
206: public void executeUpdate(String sql) throws SQLException {
207: Statement stmnt = getStatement();
208: getStatement().executeUpdate(sql);
209: closeStatement(stmnt);
210: }
211:
212: /** Execute the input PreparedStatement.
213: * The calling program is expected to close the ResultSet(s), if any, that might be returned by executing the update.
214: * @param stmt the PreparedStatement to execute.
215: * @throws SQLException if any database error occurs.
216: */
217: public void executeUpdate(PreparedStatement stmt)
218: throws SQLException {
219: stmt.execute();
220: }
221:
222: /** Returns the PreparedStatement object for the input sql. The PreparedStatement object will be cached for the connection.
223: * @param sql the sql statement which needs to be precompiled.
224: * @throws SQLException if any database error occurs.
225: * @return the PreparedStatement object.
226: */
227: public PreparedStatement getPreparedStatement(String sql)
228: throws SQLException {
229: PreparedStatement pstmt = null;
230:
231: pstmt = (PreparedStatement) m_preparedStatementCache.get(sql);
232:
233: if (pstmt == null) {
234: pstmt = m_connection.prepareStatement(sql);
235: m_preparedStatementCache.put(sql, pstmt);
236: } else {
237: pstmt.clearBatch();
238: pstmt.clearParameters();
239: pstmt.clearWarnings();
240: }
241: return pstmt;
242: }
243:
244: /** Returns the CallableStatement object for the input sql. The CallableStatement object will be cached for the connection.
245: * @param sql the sql statement.
246: * @throws SQLException if any database error occurs.
247: * @return the CallableStatement object.
248: */
249: public CallableStatement getCallableStatement(String sql)
250: throws SQLException {
251: CallableStatement cstmt = null;
252:
253: cstmt = (CallableStatement) m_callableStatementCache.get(sql);
254:
255: if (cstmt == null) {
256: cstmt = m_connection.prepareCall(sql);
257: m_callableStatementCache.put(sql, cstmt);
258: } else {
259: cstmt.clearBatch();
260: cstmt.clearParameters();
261: cstmt.clearWarnings();
262: }
263: return cstmt;
264: }
265:
266: /** Commits all changes executed against the persistent store.
267: * @throws SQLException if any database error occurs.
268: */
269: public void commit() throws SQLException {
270: m_connection.commit();
271:
272: // close all active Statement/ResultSet objects
273: closeStatements();
274: }
275:
276: /** Rollback the changes executed against the persistent store.
277: * @throws SQLException if any database error occurs.
278: */
279: public void rollback() throws SQLException {
280: m_connection.rollback();
281:
282: // close all active Statement/ResultSet objects
283: closeStatements();
284: }
285:
286: /** Getter for property hitlistSize.
287: * @return Value of property hitlistSize.
288: */
289: public Integer getHitlistSize() {
290: return m_hitlistSize;
291: }
292:
293: /** Getter for property engineType, as defined in init.xml
294: * @return Value of property engineType.
295: */
296: public String getEngineType() {
297: return m_engineType;
298: }
299:
300: /** Getter for property usePreparedStatement.
301: * @return Value of property usePreparedStatement.
302: *
303: */
304: public Boolean getUsePreparedStatement() {
305: return m_usePreparedStatement;
306: }
307:
308: // *** PRIVATE methods ****
309:
310: /** Close all the Statement objects stored in the collection, and clear the collection.*/
311: private void closeStatements() throws SQLException {
312: if (log.isDebugEnabled())
313: log.debug("Closing " + m_statements.size() + " Statements");
314: for (Iterator i = m_statements.iterator(); i.hasNext();)
315: ((Statement) i.next()).close();
316: m_statements.clear();
317: }
318: }
|