001 package org.apache.ojb.broker.accesslayer;
002
003 /* Copyright 2002-2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 import java.lang.ref.WeakReference;
019 import java.sql.ResultSet;
020 import java.sql.SQLException;
021 import java.util.Collection;
022 import java.util.HashMap;
023 import java.util.List;
024 import java.util.Map;
025 import java.util.NoSuchElementException;
026 import java.util.Vector;
027
028 import org.apache.ojb.broker.Identity;
029 import org.apache.ojb.broker.OJBRuntimeException;
030 import org.apache.ojb.broker.PBLifeCycleEvent;
031 import org.apache.ojb.broker.PBStateEvent;
032 import org.apache.ojb.broker.PBStateListener;
033 import org.apache.ojb.broker.PersistenceBrokerException;
034 import org.apache.ojb.broker.PersistenceBrokerInternal;
035 import org.apache.ojb.broker.PersistenceBrokerSQLException;
036 import org.apache.ojb.broker.cache.MaterializationCache;
037 import org.apache.ojb.broker.cache.ObjectCacheInternal;
038 import org.apache.ojb.broker.core.PersistenceBrokerImpl;
039 import org.apache.ojb.broker.metadata.ClassDescriptor;
040 import org.apache.ojb.broker.metadata.DescriptorRepository;
041 import org.apache.ojb.broker.metadata.FieldDescriptor;
042 import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
043 import org.apache.ojb.broker.query.Query;
044 import org.apache.ojb.broker.query.QueryBySQL;
045 import org.apache.ojb.broker.util.logging.Logger;
046 import org.apache.ojb.broker.util.logging.LoggerFactory;
047
048 /**
049 * RsIterator can be used to iterate over a jdbc ResultSet to retrieve
050 * persistent objects step-by-step and not all at once. In fact the
051 * PersistenceBroker::getCollectionByQuery(...) method uses a RsIterator
052 * internally to build up a Collection of objects
053 *
054 * <p>
055 * NOTE: OJB is very strict in handling <tt>RsIterator</tt> instances. <tt>RsIterator</tt> is
056 * bound very closely to the used {@link org.apache.ojb.broker.PersistenceBroker} instance.
057 * Thus if you do a
058 * <br/> - {@link org.apache.ojb.broker.PersistenceBroker#close}
059 * <br/> - {@link org.apache.ojb.broker.PersistenceBroker#commitTransaction}
060 * <br/> - {@link org.apache.ojb.broker.PersistenceBroker#abortTransaction}
061 * <br/>
062 * call, the current <tt>RsIterator</tt> instance resources will be cleaned up automatic
063 * and invalidate current instance.
064 * </p>
065 *
066 * <p>
067 * NOTE: this code uses features that only JDBC 2.0 compliant Drivers support.
068 * It will NOT work with JDBC 1.0 Drivers (e.g. SUN's JdbcOdbcDriver) If you
069 * are forced to use such a driver, you can use code from the 0.1.30 release.
070 * </p>
071 * @author <a href="mailto:thma@apache.org">Thomas Mahler <a>
072 * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird <a>- added the
073 * support for extents mapped to single table - added the .size
074 * functionality - added cursor control
075 *
076 * @version $Id: RsIterator.java,v 1.1 2007-08-24 22:17:30 ewestfal Exp $
077 */
078 public class RsIterator implements OJBIterator
079 {
080 protected Logger logger = LoggerFactory.getLogger(this.getClass());
081 private static final String INFO_MSG = "Resources already cleaned up, recommend to set" +
082 " this flag before first use of the iterator";
083 /*
084 * arminw: to improve performance we only use this instance to fire events
085 * and set the target object on every use TODO: Find a better solution
086 */
087 private PBLifeCycleEvent afterLookupEvent;
088 private MaterializationCache m_cache;
089
090 /**
091 * reference to the PersistenceBroker
092 */
093 private PersistenceBrokerImpl m_broker;
094
095 /**
096 * the underlying resultset
097 */
098 private ResultSetAndStatement m_rsAndStmt;
099
100 /**
101 * the underlying query object
102 */
103 private RsQueryObject m_queryObject;
104
105 /**
106 * the proxy class to be used or null
107 */
108 private Class m_itemProxyClass;
109
110 /**
111 * the top-level class of the item objects
112 */
113 private Class m_itemTopLevelClass = null;
114
115 /**
116 * this container holds the values of the current ro during materialisation
117 */
118 private Map m_row = null;
119
120 /**
121 * flag that indicates wether hasNext on m_rs has allready been called
122 */
123 private boolean m_hasCalledCheck = false;
124
125 /**
126 * prefetch relationship: inBatchedMode true prevents releasing of
127 * DbResources IN_LIMIT defines the max number of values of sql (IN) , -1
128 * for no limits
129 */
130 private boolean m_inBatchedMode = false;
131
132 /**
133 * return value of the previously called hasNext from m_rs
134 */
135 private boolean hasNext = false;
136
137 private boolean advancedJDBCSupport = false;
138 private boolean JDBCSupportAssessed = false;
139 private int m_current_row = 0;
140 /**
141 * Tracks whether or not the resources that are used by this class have been released.
142 */
143 private boolean resourcesAreReleased = false;
144
145 /**
146 * Flag that indicates if the automatic resource cleanup should be
147 * done or not. Default is <tt>true</tt>.
148 */
149 private boolean autoRelease = true;
150 private ResourceWrapper resourceListener;
151
152 /** if true do not fire PBLifeCycleEvent. */
153 private boolean disableLifeCycleEvents = false;
154
155 /**
156 * RsIterator constructor.
157 *
158 * @param queryObject query object
159 * @param broker the broker we should use.
160 */
161 public RsIterator(RsQueryObject queryObject, final PersistenceBrokerImpl broker)
162 {
163 setCache(broker.getInternalCache());
164 setRow(new HashMap());
165 setBroker(broker);
166 setQueryObject(queryObject);
167
168 Class classToPrefetch = broker.getReferenceBroker().getClassToPrefetch();
169 if ((classToPrefetch != null) && classToPrefetch.isAssignableFrom(queryObject.getClassDescriptor().getClassOfObject()))
170 {
171 setItemProxyClass(null);
172 }
173 else
174 {
175 setItemProxyClass(queryObject.getClassDescriptor().getProxyClass());
176 }
177
178 /*
179 * arminw: to improve performance we only use this instance to fire
180 * events and set the target object on every use TODO: Find a better
181 * solution
182 */
183 setAfterLookupEvent(new PBLifeCycleEvent(getBroker(), PBLifeCycleEvent.Type.AFTER_LOOKUP));
184
185 try
186 {
187 setRsAndStmt(queryObject.performQuery(broker.serviceJdbcAccess()));
188 /*
189 * TODO: how does prefetchRelationships handle QueryBySQL instances? Is
190 * it ok to pass query object?
191 */
192 prefetchRelationships(queryObject.getQuery());
193 if (logger.isDebugEnabled())
194 {
195 logger.debug("RsIterator[" + queryObject + "] initialized");
196 }
197 }
198 catch (RuntimeException e)
199 {
200 autoReleaseDbResources();
201 throw e;
202 }
203
204 /*
205 now RsIterator instance is created, we wrap this instance with a
206 PBStateListener to make sure that resources of this instance will be
207 released. Add this as temporary PBStateListener.
208 */
209 resourceListener = new ResourceWrapper(this);
210 m_broker.addListener(resourceListener);
211 }
212
213 protected Class getTopLevelClass()
214 {
215 if (m_itemTopLevelClass == null)
216 {
217 m_itemTopLevelClass = getBroker().getTopLevelClass(getQueryObject().getClassDescriptor().getClassOfObject());
218 }
219 return m_itemTopLevelClass;
220 }
221
222 /**
223 * returns true if there are still more rows in the underlying ResultSet.
224 * Returns false if ResultSet is exhausted.
225 */
226 public synchronized boolean hasNext()
227 {
228 try
229 {
230 if (!isHasCalledCheck())
231 {
232 setHasCalledCheck(true);
233 setHasNext(getRsAndStmt().m_rs.next());
234 if (!getHasNext())
235 {
236 autoReleaseDbResources();
237 }
238 }
239 }
240 catch (Exception ex)
241 {
242 setHasNext(false);
243 autoReleaseDbResources();
244 if(ex instanceof ResourceClosedException)
245 {
246 throw (ResourceClosedException)ex;
247 }
248 if(ex instanceof SQLException)
249 {
250 throw new PersistenceBrokerSQLException("Calling ResultSet.next() failed", (SQLException) ex);
251 }
252 else
253 {
254 throw new PersistenceBrokerException("Can't get next row from ResultSet", ex);
255 }
256 }
257 if (logger.isDebugEnabled())
258 logger.debug("hasNext() -> " + getHasNext());
259
260 return getHasNext();
261 }
262
263 /**
264 * moves to the next row of the underlying ResultSet and returns the
265 * corresponding Object materialized from this row.
266 */
267 public synchronized Object next() throws NoSuchElementException
268 {
269 try
270 {
271 if (!isHasCalledCheck())
272 {
273 hasNext();
274 }
275 setHasCalledCheck(false);
276 if (getHasNext())
277 {
278 Object obj = getObjectFromResultSet();
279 m_current_row++;
280
281 // Invoke events on PersistenceBrokerAware instances and listeners
282 // set target object
283 if (!disableLifeCycleEvents)
284 {
285 getAfterLookupEvent().setTarget(obj);
286 getBroker().fireBrokerEvent(getAfterLookupEvent());
287 getAfterLookupEvent().setTarget(null);
288 }
289 return obj;
290 }
291 else
292 {
293 throw new NoSuchElementException("inner hasNext was false");
294 }
295 }
296 catch (ResourceClosedException ex)
297 {
298 autoReleaseDbResources();
299 throw ex;
300 }
301 catch (NoSuchElementException ex)
302 {
303 autoReleaseDbResources();
304 logger.error("Error while iterate ResultSet for query " + m_queryObject, ex);
305 throw new NoSuchElementException("Could not obtain next object: " + ex.getMessage());
306 }
307 }
308
309 /**
310 * removing is not supported
311 */
312 public void remove()
313 {
314 throw new UnsupportedOperationException("removing not supported by RsIterator");
315 }
316
317 /**
318 * read all objects of this iterator. objects will be placed in cache
319 */
320 private Collection getOwnerObjects()
321 {
322 Collection owners = new Vector();
323 while (hasNext())
324 {
325 owners.add(next());
326 }
327 return owners;
328 }
329
330 /**
331 * prefetch defined relationships requires JDBC level 2.0, does not work
332 * with Arrays
333 */
334 private void prefetchRelationships(Query query)
335 {
336 List prefetchedRel;
337 Collection owners;
338 String relName;
339 RelationshipPrefetcher[] prefetchers;
340
341 if (query == null || query.getPrefetchedRelationships() == null || query.getPrefetchedRelationships().isEmpty())
342 {
343 return;
344 }
345
346 if (!supportsAdvancedJDBCCursorControl())
347 {
348 logger.info("prefetching relationships requires JDBC level 2.0");
349 return;
350 }
351
352 // prevent releasing of DBResources
353 setInBatchedMode(true);
354
355 prefetchedRel = query.getPrefetchedRelationships();
356 prefetchers = new RelationshipPrefetcher[prefetchedRel.size()];
357
358 // disable auto retrieve for all prefetched relationships
359 for (int i = 0; i < prefetchedRel.size(); i++)
360 {
361 relName = (String) prefetchedRel.get(i);
362 prefetchers[i] = getBroker().getRelationshipPrefetcherFactory()
363 .createRelationshipPrefetcher(getQueryObject().getClassDescriptor(), relName);
364 prefetchers[i].prepareRelationshipSettings();
365 }
366
367 // materialize ALL owners of this Iterator
368 owners = getOwnerObjects();
369
370 // prefetch relationships and associate with owners
371 for (int i = 0; i < prefetchedRel.size(); i++)
372 {
373 prefetchers[i].prefetchRelationship(owners);
374 }
375
376 // reset auto retrieve for all prefetched relationships
377 for (int i = 0; i < prefetchedRel.size(); i++)
378 {
379 prefetchers[i].restoreRelationshipSettings();
380 }
381
382 try
383 {
384 getRsAndStmt().m_rs.beforeFirst(); // reposition resultset jdbc 2.0
385 }
386 catch (SQLException e)
387 {
388 logger.error("beforeFirst failed !", e);
389 }
390
391 setInBatchedMode(false);
392 setHasCalledCheck(false);
393 }
394
395 /**
396 * returns an Identity object representing the current resultset row
397 */
398 protected Identity getIdentityFromResultSet() throws PersistenceBrokerException
399 {
400 // fill primary key values from Resultset
401 FieldDescriptor fld;
402 FieldDescriptor[] pkFields = getQueryObject().getClassDescriptor().getPkFields();
403 Object[] pkValues = new Object[pkFields.length];
404
405 for (int i = 0; i < pkFields.length; i++)
406 {
407 fld = pkFields[i];
408 pkValues[i] = getRow().get(fld.getColumnName());
409 }
410
411 // return identity object build up from primary keys
412 return getBroker().serviceIdentity().buildIdentity(
413 getQueryObject().getClassDescriptor().getClassOfObject(), getTopLevelClass(), pkValues);
414 }
415
416 /**
417 * returns a fully materialized Object from the current row of the
418 * underlying resultset. Works as follows: - read Identity from the primary
419 * key values of current row - check if Object is in cache - return cached
420 * object or read it from current row and put it in cache
421 */
422 protected Object getObjectFromResultSet() throws PersistenceBrokerException
423 {
424 getRow().clear();
425 /**
426 * MBAIRD if a proxy is to be used, return a proxy instance and dont
427 * perfom a full materialization. NOTE: Potential problem here with
428 * multi-mapped table. The itemProxyClass is for the m_cld
429 * classdescriptor. The object you are materializing might not be of
430 * that type, it could be a subclass. We should get the concrete class
431 * type out of the resultset then check the proxy from that.
432 * itemProxyClass should NOT be a member variable.
433 */
434
435 RowReader rowReader = getQueryObject().getClassDescriptor().getRowReader();
436 // in any case we need the PK values of result set row
437 // provide m_row with primary key data of current row
438 rowReader.readPkValuesFrom(getRsAndStmt(), getRow());
439
440 if (getItemProxyClass() != null)
441 {
442 // assert: m_row is filled with primary key values from db
443 return getProxyFromResultSet();
444 }
445 else
446 {
447 // 1.read Identity
448 Identity oid = getIdentityFromResultSet();
449 Object result;
450
451 // 2. check if Object is in cache. if so return cached version.
452 result = getCache().lookup(oid);
453 if (result == null)
454 {
455
456 // map all field values from the current result set
457 rowReader.readObjectArrayFrom(getRsAndStmt(), getRow());
458 // 3. If Object is not in cache
459 // materialize Object with primitive attributes filled from current row
460 result = rowReader.readObjectFrom(getRow());
461 // result may still be null!
462 if (result != null)
463 {
464 /*
465 * synchronize on result so the ODMG-layer can take a
466 * snapshot only of fully cached (i.e. with all references +
467 * collections) objects
468 */
469 synchronized (result)
470 {
471 getCache().enableMaterializationCache();
472 try
473 {
474 getCache().doInternalCache(oid, result, ObjectCacheInternal.TYPE_NEW_MATERIALIZED);
475 /**
476 * MBAIRD if you have multiple classes mapped to a
477 * table, and you query on the base class you could get
478 * back NON base class objects, so we shouldn't pass
479 * m_cld, but rather the class descriptor for the
480 * actual class.
481 */
482 // fill reference and collection attributes
483 ClassDescriptor cld = getBroker().getClassDescriptor(result.getClass());
484 // don't force loading of reference
485 final boolean unforced = false;
486 // Maps ReferenceDescriptors to HashSets of owners
487 getBroker().getReferenceBroker().retrieveReferences(result, cld, unforced);
488 getBroker().getReferenceBroker().retrieveCollections(result, cld, unforced);
489 getCache().disableMaterializationCache();
490 }
491 catch(RuntimeException e)
492 {
493 // catch runtime exc. to guarantee clearing of internal buffer on failure
494 getCache().doLocalClear();
495 throw e;
496 }
497 }
498 }
499 }
500 else // Object is in cache
501 {
502 ClassDescriptor cld = getBroker().getClassDescriptor(result.getClass());
503 // if refresh is required, read all values from the result set and
504 // update the cache instance from the db
505 if (cld.isAlwaysRefresh())
506 {
507 // map all field values from the current result set
508 rowReader.readObjectArrayFrom(getRsAndStmt(), getRow());
509 rowReader.refreshObject(result, getRow());
510 }
511 getBroker().checkRefreshRelationships(result, oid, cld);
512 }
513
514 return result;
515 }
516 }
517
518 /**
519 * Reads primary key information from current RS row and generates a
520 *
521 * corresponding Identity, and returns a proxy from the Identity.
522 *
523 * @throws PersistenceBrokerException
524 * if there was an error creating the proxy class
525 */
526 protected Object getProxyFromResultSet() throws PersistenceBrokerException
527 {
528 // 1. get Identity of current row:
529 Identity oid = getIdentityFromResultSet();
530
531 // 2. return a Proxy instance:
532 return getBroker().createProxy(getItemProxyClass(), oid);
533 }
534
535 /**
536 * with a new batch of JDBC 3.0 drivers coming out we can't just check for
537 * begins with 2, we need to check the actual version and see if it's
538 * greater than or equal to 2.
539 */
540 private boolean supportsAdvancedJDBCCursorControl()
541 {
542 if (!JDBCSupportAssessed)
543 {
544 if (getConnectionDescriptor().getJdbcLevel() >= 2.0)
545 advancedJDBCSupport = true;
546 JDBCSupportAssessed = true;
547 }
548 return advancedJDBCSupport;
549 }
550
551 /**
552 * Answer the counted size
553 *
554 * @return int
555 */
556 protected int countedSize() throws PersistenceBrokerException
557 {
558 Query countQuery = getBroker().serviceBrokerHelper().getCountQuery(getQueryObject().getQuery());
559 ResultSetAndStatement rsStmt;
560 ClassDescriptor cld = getQueryObject().getClassDescriptor();
561 int count = 0;
562
563 // BRJ: do not use broker.getCount() because it's extent-aware
564 // the count we need here must not include extents !
565 if (countQuery instanceof QueryBySQL)
566 {
567 String countSql = ((QueryBySQL) countQuery).getSql();
568 rsStmt = getBroker().serviceJdbcAccess().executeSQL(countSql, cld, Query.NOT_SCROLLABLE);
569 }
570 else
571 {
572 rsStmt = getBroker().serviceJdbcAccess().executeQuery(countQuery, cld);
573 }
574
575 try
576 {
577 if (rsStmt.m_rs.next())
578 {
579 count = rsStmt.m_rs.getInt(1);
580 }
581 }
582 catch (SQLException e)
583 {
584 throw new PersistenceBrokerException(e);
585 }
586 finally
587 {
588 rsStmt.close();
589 }
590
591 return count;
592 }
593
594 /**
595 * @return the size of the iterator, aka the number of rows in this
596 * iterator.
597 */
598 public int size() throws PersistenceBrokerException
599 {
600 int retval = 0; // default size is 0;
601 boolean forwardOnly = true;
602 try
603 {
604 forwardOnly = getRsAndStmt().m_stmt.getResultSetType() == ResultSet.TYPE_FORWARD_ONLY;
605 }
606 catch (SQLException e)
607 {
608 //ignore it
609 }
610 if (!supportsAdvancedJDBCCursorControl()
611 || getBroker().serviceConnectionManager().getSupportedPlatform().useCountForResultsetSize()
612 || forwardOnly)
613 {
614 /**
615 * MBAIRD: doesn't support the .last .getRow method, use the
616 * .getCount on the persistenceBroker which executes a count(*)
617 * query.
618 */
619 if (logger.isDebugEnabled())
620 logger.debug("Executing count(*) to get size()");
621 retval = countedSize();
622 }
623 else
624 {
625 /**
626 * Use the .last .getRow method of finding size. The reason for
627 * supplying an alternative method is effeciency, some driver/db
628 * combos are a lot more efficient at just moving the cursor and
629 * returning the row in a real (not -1) number.
630 */
631 int whereIAm; // first
632 try
633 {
634 if (getRsAndStmt().m_rs != null)
635 {
636 whereIAm = getRsAndStmt().m_rs.getRow();
637 if (getRsAndStmt().m_rs.last())
638 {
639 retval = getRsAndStmt().m_rs.getRow();
640 }
641 else
642 {
643 retval = 0;
644 }
645 // go back from whence I came.
646 if (whereIAm > 0)
647 {
648 getRsAndStmt().m_rs.absolute(whereIAm);
649 }
650 else
651 {
652 getRsAndStmt().m_rs.beforeFirst();
653 }
654 }
655 }
656 catch (SQLException se)
657 {
658 advancedJDBCSupport = false;
659 }
660 }
661 return retval;
662 }
663
664 /* (non-Javadoc)
665 * @see org.apache.ojb.broker.accesslayer.OJBIterator#fullSize()
666 */
667 public int fullSize() throws PersistenceBrokerException
668 {
669 return size();
670 }
671
672 /**
673 * Moves the cursor to the given row number in the iterator. If the row
674 * number is positive, the cursor moves to the given row number with
675 * respect to the beginning of the iterator. The first row is row 1, the
676 * second is row 2, and so on.
677 *
678 * @param row the row to move to in this iterator, by absolute number
679 */
680 public boolean absolute(int row) throws PersistenceBrokerException
681 {
682 boolean retval;
683 if (supportsAdvancedJDBCCursorControl())
684 {
685 retval = absoluteAdvanced(row);
686 }
687 else
688 {
689 retval = absoluteBasic(row);
690 }
691 return retval;
692 }
693
694 /**
695 * absolute for basicJDBCSupport
696 * @param row
697 */
698 private boolean absoluteBasic(int row)
699 {
700 boolean retval = false;
701
702 if (row > m_current_row)
703 {
704 try
705 {
706 while (m_current_row < row && getRsAndStmt().m_rs.next())
707 {
708 m_current_row++;
709 }
710 if (m_current_row == row)
711 {
712 retval = true;
713 }
714 else
715 {
716 setHasCalledCheck(true);
717 setHasNext(false);
718 retval = false;
719 autoReleaseDbResources();
720 }
721 }
722 catch (Exception ex)
723 {
724 setHasCalledCheck(true);
725 setHasNext(false);
726 retval = false;
727 }
728 }
729 else
730 {
731 logger.info("Your driver does not support advanced JDBC Functionality, " +
732 "you cannot call absolute() with a position < current");
733 }
734 return retval;
735 }
736
737 /**
738 * absolute for advancedJDBCSupport
739 * @param row
740 */
741 private boolean absoluteAdvanced(int row)
742 {
743 boolean retval = false;
744
745 try
746 {
747 if (getRsAndStmt().m_rs != null)
748 {
749 if (row == 0)
750 {
751 getRsAndStmt().m_rs.beforeFirst();
752 }
753 else
754 {
755 retval = getRsAndStmt().m_rs.absolute(row);
756 }
757 m_current_row = row;
758 setHasCalledCheck(false);
759 }
760 }
761 catch (SQLException e)
762 {
763 advancedJDBCSupport = false;
764 }
765 return retval;
766 }
767
768 /**
769 * Moves the cursor a relative number of rows, either positive or negative.
770 * Attempting to move beyond the first/last row in the iterator positions
771 * the cursor before/after the the first/last row. Calling relative(0) is
772 * valid, but does not change the cursor position.
773 *
774 * @param row
775 * the row to move to in this iterator, by relative number
776 */
777 public boolean relative(int row) throws PersistenceBrokerException
778 {
779 boolean retval = false;
780 if (supportsAdvancedJDBCCursorControl())
781 {
782 try
783 {
784 if (getRsAndStmt().m_rs != null)
785 {
786 retval = getRsAndStmt().m_rs.relative(row);
787 m_current_row += row;
788 }
789 }
790 catch (SQLException e)
791 {
792 advancedJDBCSupport = false;
793 }
794 }
795 else
796 {
797 if (row >= 0)
798 {
799 return absolute(m_current_row + row);
800 }
801 else
802 {
803 logger.info("Your driver does not support advanced JDBC Functionality, you cannot call relative() with a negative value");
804 }
805 }
806 return retval;
807 }
808
809 /**
810 * Release all internally used Database resources of the iterator. Clients
811 * must call this methods explicitely if the iterator is not exhausted by
812 * the client application. If the Iterator is exhauseted this method will
813 * be called implicitely.
814 */
815 public void releaseDbResources()
816 {
817 release(true);
818 }
819
820 void release(boolean removeResourceListener)
821 {
822 if (!isInBatchedMode()) // resources are reused in batched mode
823 {
824 // If we haven't released resources yet, then do so.
825 if (!this.resourcesAreReleased)
826 {
827 // remove the resource listener
828 if(removeResourceListener && resourceListener != null)
829 {
830 try
831 {
832 /*
833 when RsIterator is closed, the resource listener
834 was no longer needed to listen on PB events for clean up.
835 */
836 m_broker.removeListener(resourceListener);
837 this.resourceListener = null;
838 }
839 catch(Exception e)
840 {
841 logger.error("Error when try to remove RsIterator resource listener", e);
842 }
843 }
844 this.resourcesAreReleased = true;
845 if (m_rsAndStmt != null)
846 {
847 m_rsAndStmt.close();
848 m_rsAndStmt = null;
849 }
850 }
851 }
852 }
853
854 /**
855 * Internally used by this class to close used resources
856 * as soon as possible.
857 */
858 protected void autoReleaseDbResources()
859 {
860 if(autoRelease)
861 {
862 releaseDbResources();
863 }
864 }
865
866 /**
867 * Allows user to switch off/on automatic resource cleanup.
868 * Set <tt>false</tt> to take responsibility of resource cleanup
869 * for this class, means after use it's mandatory to call
870 * {@link #releaseDbResources}.
871 * <br/> By default it's <tt>true</tt> and resource cleanup is done
872 * automatic.
873 */
874 public void setAutoRelease(boolean autoRelease)
875 {
876 /*
877 arminw:
878 this method should be declared in OJBIterator interface till
879 OJB 1.1 and PersistenceBroker interface should only return
880 OJBIterator instead of Iterator instances
881 */
882 if(resourcesAreReleased && !autoRelease)
883 {
884 logger.info(INFO_MSG);
885 }
886 this.autoRelease = autoRelease;
887 }
888
889 /**
890 * Return the DescriptorRepository
891 */
892 protected DescriptorRepository getDescriptorRepository()
893 {
894 return getBroker().getDescriptorRepository();
895 }
896
897 protected JdbcConnectionDescriptor getConnectionDescriptor()
898 {
899 return getBroker().serviceConnectionManager().getConnectionDescriptor();
900 }
901
902 /**
903 * safety just in case someone leaks.
904 */
905 protected void finalize()
906 {
907 if (m_rsAndStmt != null)
908 {
909 logger.info("Found unclosed resources while finalize (causer class: " + this.getClass().getName() + ")" +
910 " Do automatic cleanup");
911 releaseDbResources();
912 }
913 try
914 {
915 super.finalize();
916 }
917 catch(Throwable throwable)
918 {
919 throwable.printStackTrace();
920 }
921 }
922
923 public String toString()
924 {
925 return super.toString();
926 }
927
928 /**
929 * @return Returns the cld.
930 */
931 public ClassDescriptor getClassDescriptor()
932 {
933 return getQueryObject().getClassDescriptor();
934 }
935
936 protected void setBroker(PersistenceBrokerImpl broker)
937 {
938 m_broker = broker;
939 }
940
941 protected PersistenceBrokerInternal getBroker()
942 {
943 return m_broker;
944 }
945
946 protected void setRsAndStmt(ResultSetAndStatement rsAndStmt)
947 {
948 if(m_rsAndStmt != null)
949 {
950 throw new ResourceNotClosedException("Unclosed resources found, please release resources" +
951 " before set new ones");
952 }
953 resourcesAreReleased = false;
954 m_rsAndStmt = rsAndStmt;
955 }
956
957 protected ResultSetAndStatement getRsAndStmt()
958 {
959 if(resourcesAreReleased)
960 {
961 throw new ResourceClosedException("Resources no longer reachable, RsIterator will be automatic" +
962 " cleaned up on PB.close/.commitTransaction/.abortTransaction");
963 }
964 return m_rsAndStmt;
965 }
966
967 protected void setQueryObject(RsQueryObject queryObject)
968 {
969 this.m_queryObject = queryObject;
970 }
971
972 protected RsQueryObject getQueryObject()
973 {
974 return m_queryObject;
975 }
976
977 protected void setItemProxyClass(Class itemProxyClass)
978 {
979 this.m_itemProxyClass = itemProxyClass;
980 }
981
982 protected Class getItemProxyClass()
983 {
984 return m_itemProxyClass;
985 }
986
987 protected void setRow(Map row)
988 {
989 m_row = row;
990 }
991
992 protected Map getRow()
993 {
994 return m_row;
995 }
996
997 protected void setCache(MaterializationCache cache)
998 {
999 this.m_cache = cache;
1000 }
1001
1002 protected MaterializationCache getCache()
1003 {
1004 return m_cache;
1005 }
1006
1007 protected void setAfterLookupEvent(PBLifeCycleEvent afterLookupEvent)
1008 {
1009 this.afterLookupEvent = afterLookupEvent;
1010 }
1011
1012 protected PBLifeCycleEvent getAfterLookupEvent()
1013 {
1014 return afterLookupEvent;
1015 }
1016
1017 protected void setHasCalledCheck(boolean hasCalledCheck)
1018 {
1019 this.m_hasCalledCheck = hasCalledCheck;
1020 }
1021
1022 protected boolean isHasCalledCheck()
1023 {
1024 return m_hasCalledCheck;
1025 }
1026
1027 protected void setHasNext(boolean hasNext)
1028 {
1029 this.hasNext = hasNext;
1030 }
1031
1032 protected boolean getHasNext()
1033 {
1034 return hasNext;
1035 }
1036
1037 protected void setInBatchedMode(boolean inBatchedMode)
1038 {
1039 this.m_inBatchedMode = inBatchedMode;
1040 }
1041
1042 protected boolean isInBatchedMode()
1043 {
1044 return m_inBatchedMode;
1045 }
1046
1047 //***********************************************************
1048 // inner classes
1049 //***********************************************************
1050 /**
1051 * Wraps a {@link RsIterator} instance as {@link WeakReference}.
1052 */
1053 public static class ResourceWrapper implements PBStateListener
1054 {
1055 /*
1056 arminw:
1057 we do register a PBStateListener to PB instance
1058 to make sure that this instance will be cleaned up at PB.close() call.
1059 If PB was in tx, we cleanup resources on PB.commit/abort, because
1060 commit/abort close the current used connection and all Statement/ResultSet
1061 instances will become invalid.
1062 */
1063 WeakReference ref;
1064
1065 public ResourceWrapper(RsIterator rs)
1066 {
1067 ref = new WeakReference(rs);
1068 }
1069
1070 public void beforeClose(PBStateEvent event)
1071 {
1072 if(ref != null)
1073 {
1074 RsIterator rs = (RsIterator) ref.get();
1075 if(rs != null) rs.release(false);
1076 ref = null;
1077 }
1078 }
1079
1080 public void beforeRollback(PBStateEvent event)
1081 {
1082 if(ref != null)
1083 {
1084 RsIterator rs = (RsIterator) ref.get();
1085 if(rs != null) rs.release(false);
1086 ref = null;
1087 }
1088 }
1089
1090 public void beforeCommit(PBStateEvent event)
1091 {
1092 if(ref != null)
1093 {
1094 RsIterator rs = (RsIterator) ref.get();
1095 if(rs != null) rs.release(false);
1096 ref = null;
1097 }
1098 }
1099
1100 public void afterCommit(PBStateEvent event)
1101 {
1102 //do nothing
1103 }
1104 public void afterRollback(PBStateEvent event)
1105 {
1106 //do nothing
1107 }
1108 public void afterBegin(PBStateEvent event)
1109 {
1110 //do nothing
1111 }
1112 public void beforeBegin(PBStateEvent event)
1113 {
1114 //do nothing
1115 }
1116 public void afterOpen(PBStateEvent event)
1117 {
1118 //do nothing
1119 }
1120 }
1121
1122 public static class ResourceClosedException extends OJBRuntimeException
1123 {
1124 public ResourceClosedException(String msg)
1125 {
1126 super(msg);
1127 }
1128
1129 public ResourceClosedException(String msg, Throwable cause)
1130 {
1131 super(msg, cause);
1132 }
1133 }
1134
1135 public static class ResourceNotClosedException extends OJBRuntimeException
1136 {
1137 public ResourceNotClosedException(String msg)
1138 {
1139 super(msg);
1140 }
1141
1142 public ResourceNotClosedException(String msg, Throwable cause)
1143 {
1144 super(msg, cause);
1145 }
1146 }
1147
1148 /**
1149 * @see org.apache.ojb.broker.accesslayer.OJBIterator#disableLifeCycleEvents()
1150 */
1151 public void disableLifeCycleEvents()
1152 {
1153 disableLifeCycleEvents = true;
1154 }
1155 }