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    }