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.ResultSet;
056: import java.sql.SQLException;
057: import java.lang.reflect.InvocationTargetException;
058:
059: import org.jaffa.persistence.Criteria;
060: import org.jaffa.persistence.IPersistent;
061: import org.jaffa.persistence.engines.jdbcengine.configservice.ClassMetaData;
062: import org.jaffa.persistence.engines.jdbcengine.util.MoldingService;
063: import org.jaffa.persistence.exceptions.PostLoadFailedException;
064: import org.jaffa.persistence.engines.jdbcengine.datasource.exceptions.DataSourceCursorRuntimeException;
065: import org.jaffa.persistence.engines.jdbcengine.querygenerator.JdbcBridge;
066:
067: /** This class gives a Collection view of a ResultSet. Each row of the ResultSet is molded into an appropriate Persistent object and added to the Collection.
068: * The rows are fetched in groups. The size of each group is determined by the 'hitlistSize' parameter of the init.xml for a particular database definition.
069: * On creation, the 1st group of rows are fetched. Use the Iterator (a custom implementation), to fetch all the rows. The Collection interface methods will only on the fetched data.
070: * The size() method will return a negative value, unless all the data has been fetched.
071: */
072: public class DataSourceCursor implements Collection {
073:
074: private static final Logger log = Logger
075: .getLogger(DataSourceCursor.class);
076:
077: private DataSource m_dataSource = null;
078: private ResultSet m_resultSet = null;
079: private ClassMetaData m_classMetaData = null;
080: private Criteria m_criteria = null;
081: private Collection m_collection = null;
082: private boolean m_allRead = false;
083:
084: /** Creates new DataSourceCursor. It loads the initial set of rows.
085: * @param dataSource the DataSource object which will be creating this object.
086: * @param resultSet the ResultSet from which the data is to be loded.
087: * @param classMetaData the ClassMetaData definition to be used for molding a row into a Persistent object.
088: * @param criteria The Criteria used for the query. This will provide the values to set the various flags on the Persistent object.
089: * @throws SQLException if any database error occurs.
090: * @throws PostLoadFailedException if any error is thrown in the PostLoad trigger of the persistent object.
091: * @throws DataSourceCursorRuntimeException if any error occurs while molding the row into the Persistent object.
092: * @throws IOException if any error occurs in reading the data from the database.
093: */
094: DataSourceCursor(DataSource dataSource, ResultSet resultSet,
095: ClassMetaData classMetaData, Criteria criteria)
096: throws SQLException, PostLoadFailedException,
097: DataSourceCursorRuntimeException, IOException {
098: m_dataSource = dataSource;
099: m_resultSet = resultSet;
100: m_classMetaData = classMetaData;
101: m_criteria = criteria;
102: m_collection = new ArrayList();
103:
104: try {
105: // set the fetch size on the ResultSet
106: m_resultSet.setFetchSize(dataSource.getHitlistSize()
107: .intValue());
108: } catch (Exception e) {
109: // NOTE: The setFetchSize feature may not be implemented by all the drivers. eg.Postgresql.
110: // so just ignore the exception
111: }
112:
113: if (log.isDebugEnabled())
114: log
115: .debug("Fetching the initial set of data using the hitlist size "
116: + dataSource.getHitlistSize().intValue());
117: fetchData();
118: }
119:
120: /** Returns a custom iterator. Use the iterator for fetching all the rows.
121: * @return an iterator over the elements in this collection.
122: */
123: public Iterator iterator() {
124: return new DataSourceCursorIterator();
125: }
126:
127: /** Returns an array containing all of the elements in this collection.
128: * Note: This will only return the data that has been fetched so far.
129: * @return an array containing all of the elements in this collection.
130: */
131: public Object[] toArray() {
132: return m_collection.toArray();
133: }
134:
135: /** Adds all of the elements in the specified collection to this collection.
136: * Note: This will not add any data to the Persistent store.
137: * @param collection elements to be inserted into this collection.
138: * @return true if this collection changed as a result of the call.
139: */
140: public boolean addAll(Collection collection) {
141: return m_collection.addAll(collection);
142: }
143:
144: /** Returns true if this collection contains all of the elements in the specified collection.
145: * Note: This will only check the data that has been fetched so far.
146: * @param collection collection to be checked for containment in this collection.
147: * @return true if this collection contains all of the elements in the specified collection.
148: */
149: public boolean containsAll(Collection collection) {
150: return m_collection.containsAll(collection);
151: }
152:
153: /** Removes a single instance of the specified element from this collection, if it is present.
154: * Note: This will not remove any data from the Persistent store. This will only check the data that has been fetched so far.
155: * @param obj element to be removed from this collection, if present.
156: * @return true if this collection changed as a result of the call.
157: */
158: public boolean remove(Object obj) {
159: return m_collection.remove(obj);
160: }
161:
162: /** Ensures that this collection contains the specified element.
163: * Note: This will not add any data to the Persistent store.
164: * @param obj element whose presence in this collection is to be ensured.
165: * @return true if this collection changed as a result of the call.
166: */
167: public boolean add(Object obj) {
168: return m_collection.add(obj);
169: }
170:
171: /** Removes all of the elements from this collection.
172: * Note: This will not have any effect on the Persistent store.
173: * This will close the underlying ResultSet.
174: * @throws DataSourceCursorRuntimeException if any error occurs in closing the underlying ResultSet.
175: */
176: public void clear() throws DataSourceCursorRuntimeException {
177: try {
178: m_collection.clear();
179: if (!m_allRead) {
180: if (log.isDebugEnabled())
181: log
182: .debug("Cleared the internal Collection. Invoking the closeStatement() method of the DataSource.");
183: m_dataSource.closeStatement(m_resultSet.getStatement());
184: m_allRead = true;
185: }
186: } catch (SQLException e) {
187: String str = "Error in closing the ResultSet";
188: log.error(str, e);
189: throw new DataSourceCursorRuntimeException(str, e);
190: }
191: }
192:
193: /** Returns true if this collection contains the specified element.
194: * Note: This will only check the data that has been fetched so far.
195: * @param obj element whose presence in this collection is to be tested.
196: * @return true if this collection contains the specified element.
197: */
198: public boolean contains(Object obj) {
199: return m_collection.contains(obj);
200: }
201:
202: /** Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array.
203: * Note: This will only return the data that has been fetched so far.
204: * @param obj the array into which the elements of this collection are to be stored, if it is big enough; otherwise, a new array of the same runtime type is allocated for this purpose.
205: * @return an array containing the elements of this collection.
206: */
207: public Object[] toArray(Object[] obj) {
208: return m_collection.toArray(obj);
209: }
210:
211: /** Returns true if this collection contains no elements. However, a 'true' does not mean that
212: * there is no more data to be fetched; since it is possible that the fetched elements could have been removed by the remove() methods.
213: * @return true if this collection contains no elements.
214: */
215: public boolean isEmpty() {
216: return m_collection.isEmpty();
217: }
218:
219: /** Removes all this collection's elements that are also contained in the specified collection.
220: * Note: This will not remove any data from the Persistent store. This will only check the data that has been fetched so far.
221: * @param collection elements to be removed from this collection.
222: * @return true if this collection changed as a result of the call.
223: */
224: public boolean removeAll(Collection collection) {
225: return m_collection.removeAll(collection);
226: }
227:
228: /** Retains only the elements in this collection that are contained in the specified collection.
229: * Note: This will not remove any data from the Persistent store. This will only check the data that has been fetched so far.
230: * @param collection elements to be retained in this collection.
231: * @return true if this collection changed as a result of the call.
232: */
233: public boolean retainAll(Collection collection) {
234: return m_collection.retainAll(collection);
235: }
236:
237: /** Returns the number of elements in this collection.
238: * Note: This will return a negative number, if all the data has not been fetched.
239: * @return the number of elements in this collection;
240: */
241: public int size() {
242: return (m_allRead ? 1 : -1) * m_collection.size();
243: }
244:
245: /** This will fetch additional data. Each row will be molded into a Persistent object
246: * and added to the collection. It will invoke the PostLoad trigger of each Persistent object.
247: * If all the data has been fetched, then the ResultSet will be closed.
248: */
249: private void fetchData() throws SQLException,
250: PostLoadFailedException, DataSourceCursorRuntimeException,
251: IOException {
252: if (!m_allRead) {
253: for (int i = 0; i < m_dataSource.getHitlistSize()
254: .intValue(); i++) {
255: if (m_resultSet.next()) {
256: try {
257: IPersistent object = MoldingService.getObject(
258: m_classMetaData, m_resultSet,
259: m_dataSource.getEngineType());
260: if (log.isDebugEnabled()) {
261: log.debug("Fetched the Persistent object: "
262: + object.toString());
263: log
264: .debug("Invoking the PostLoad trigger of the Persistent object");
265: }
266: JdbcBridge.updatePersistentFlagsOnQuery(object,
267: m_criteria);
268: object.postLoad();
269: m_collection.add(object);
270: if (log.isDebugEnabled())
271: log
272: .debug("Current size of the retrieved record set is "
273: + m_collection.size());
274:
275: } catch (ClassNotFoundException e) {
276: String str = "Error while molding a ResultSet into a Persistent object";
277: log.error(str, e);
278: throw new DataSourceCursorRuntimeException(str,
279: e);
280: } catch (InstantiationException e) {
281: String str = "Error while molding a ResultSet into a Persistent object";
282: log.error(str, e);
283: throw new DataSourceCursorRuntimeException(str,
284: e);
285: } catch (IllegalAccessException e) {
286: String str = "Error while molding a ResultSet into a Persistent object";
287: log.error(str, e);
288: throw new DataSourceCursorRuntimeException(str,
289: e);
290: } catch (InvocationTargetException e) {
291: String str = "Error while molding a ResultSet into a Persistent object";
292: log.error(str, e);
293: throw new DataSourceCursorRuntimeException(str,
294: e);
295: }
296: } else {
297: if (log.isDebugEnabled())
298: log
299: .debug("Fetched in the complete result set. Invoking the closeStatement() method of the DataSource.");
300: m_dataSource.closeStatement(m_resultSet
301: .getStatement());
302: m_allRead = true;
303: break;
304: }
305: }
306: }
307: }
308:
309: /** This is a Custom implementation of the Iterator. It fetches all the data from the ResultSet, in hitlistSize chunks.
310: */
311: private class DataSourceCursorIterator implements Iterator {
312: private Iterator m_iterator = null;
313:
314: /** Creates a DataSourceCursorIterator.*/
315: private DataSourceCursorIterator() {
316: m_iterator = m_collection.iterator();
317: }
318:
319: /** Returns true if the iteration has more elements.
320: * If no more elements exist, it fetches more data from the database in hitlistSize chunks,
321: * and returns a true if additional data has been retrieved.
322: * @throws DataSourceCursorRuntimeException if any error occurs while fetching additional data.
323: * @return Returns true if the iteration has more elements.
324: */
325: public boolean hasNext()
326: throws DataSourceCursorRuntimeException {
327: if (m_allRead) {
328: return m_iterator.hasNext();
329: } else if (m_iterator.hasNext()) {
330: return true;
331: } else {
332: try {
333: if (log.isDebugEnabled())
334: log.debug("Fetching the next set of data");
335: int oldSize = m_collection.size();
336: fetchData();
337: if (m_collection.size() > oldSize) {
338: // Create a new iterator & move back to the original position.
339: // This is probably inefficient. Could be replaced by a manual implementation like storing index etc. etc.
340: m_iterator = m_collection.iterator();
341: for (int i = 0; i < oldSize; i++)
342: m_iterator.next();
343: return true;
344: } else {
345: return false;
346: }
347: } catch (SQLException e) {
348: String str = "Error in fetching data from the database";
349: log.error(str, e);
350: throw new DataSourceCursorRuntimeException(str, e);
351: } catch (PostLoadFailedException e) {
352: String str = "Error in execution of the PostLoad trigger";
353: log.error(str, e);
354: throw new DataSourceCursorRuntimeException(str, e);
355: } catch (IOException e) {
356: String str = "Error in fetching data from the database";
357: log.error(str, e);
358: throw new DataSourceCursorRuntimeException(str, e);
359: }
360: }
361: }
362:
363: /** Returns the next element in the iteration. This will fetch additional data, if available.
364: * @throws DataSourceCursorRuntimeException if any error occurs while fetching additional data.
365: * @return the next element in the iteration.
366: */
367: public Object next() throws DataSourceCursorRuntimeException {
368: if (hasNext()) {
369: return m_iterator.next();
370: } else {
371: String str = "No more elements to iterate through";
372: log.error(str);
373: throw new java.util.NoSuchElementException(str);
374: }
375: }
376:
377: /** Removes from the underlying collection the last element returned by the iterator.
378: * Note: This will not remove any data from the Persistent store.
379: */
380: public void remove() {
381: m_iterator.remove();
382: }
383: }
384: }
|