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 }