Coverage Report - org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
CollectionProxyDefaultImpl
N/A
N/A
1.9
 
 1  
 package org.apache.ojb.broker.core.proxy;
 2  
 
 3  
 /* Copyright 2002-2005 The Apache Software Foundation
 4  
  *
 5  
  * Licensed under the Apache License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 
 18  
 import java.util.ArrayList;
 19  
 import java.util.Collection;
 20  
 import java.util.Iterator;
 21  
 
 22  
 import org.apache.ojb.broker.ManageableCollection;
 23  
 import org.apache.ojb.broker.OJBRuntimeException;
 24  
 import org.apache.ojb.broker.PBFactoryException;
 25  
 import org.apache.ojb.broker.PBKey;
 26  
 import org.apache.ojb.broker.PersistenceBroker;
 27  
 import org.apache.ojb.broker.PersistenceBrokerException;
 28  
 import org.apache.ojb.broker.PersistenceBrokerFactory;
 29  
 import org.apache.ojb.broker.metadata.MetadataManager;
 30  
 import org.apache.ojb.broker.metadata.MetadataException;
 31  
 import org.apache.ojb.broker.core.PersistenceBrokerThreadMapping;
 32  
 import org.apache.ojb.broker.query.Query;
 33  
 import org.apache.ojb.broker.util.collections.IRemovalAwareCollection;
 34  
 import org.apache.ojb.broker.util.collections.RemovalAwareCollection;
 35  
 
 36  
 /**
 37  
  * A place holder for a whole collection to support deferred loading of relationships.
 38  
  * The complete collection is loaded on access to the data.
 39  
  *
 40  
  * @author <a href="mailto:jbraeuchi@hotmail.com">Jakob Braeuchi</a>
 41  
  * @version $Id: CollectionProxyDefaultImpl.java,v 1.1 2007-08-24 22:17:32 ewestfal Exp $
 42  
  */
 43  
 public class CollectionProxyDefaultImpl implements Collection, ManageableCollection, CollectionProxy
 44  
 {
 45  
     /** The key for acquiring the above broker */
 46  
     private PBKey _brokerKey;
 47  
     /** Flag set when per-thread metadata profiles are in use. */
 48  
     private boolean _perThreadDescriptorsEnabled;
 49  
     /** Profile key used when lazy-loading with per-thread metadata profiles. */
 50  
     private Object _profileKey;
 51  
     /** The query that defines the values in the collection */
 52  
     private Query _query;
 53  
     /** The actual data (if already loaded) */
 54  
     private Collection _data;
 55  
     /** The collection type */
 56  
     private Class _collectionClass;
 57  
     /** The number of objects */
 58  
     private int _size = -1;
 59  
     /*
 60  
     arminw
 61  
     fix a bug, caused by closing PB instances
 62  
     obtained from PersistenceBrokerThreadMapping.
 63  
     TODO: Could we find a better solution for this?
 64  
     */
 65  
     private boolean _needsClose;
 66  
     /** Objects that listen on this proxy for loading events */
 67  
     private transient ArrayList _listeners;
 68  
 
 69  
     /**
 70  
      * Creates a new collection proxy (uses
 71  
      * {@link org.apache.ojb.broker.util.collections.RemovalAwareCollection}
 72  
      * as the collection class).
 73  
      * 
 74  
      * @param brokerKey The key of the persistence broker
 75  
      * @param query     The defining query
 76  
      */
 77  
     public CollectionProxyDefaultImpl(PBKey brokerKey, Query query)
 78  
     {
 79  
         this(brokerKey, RemovalAwareCollection.class, query);
 80  
     }
 81  
 
 82  
     /**
 83  
      * Creates a new collection proxy that uses the given collection type.
 84  
      * 
 85  
      * @param brokerKey The key of the persistence broker
 86  
      * @param collClass The collection type
 87  
      * @param query     The defining query
 88  
      */
 89  
     public CollectionProxyDefaultImpl(PBKey brokerKey, Class collClass, Query query)
 90  
     {
 91  
         MetadataManager mm = MetadataManager.getInstance();
 92  
         _perThreadDescriptorsEnabled = mm.isEnablePerThreadChanges();
 93  
         if (_perThreadDescriptorsEnabled)
 94  
         {
 95  
             // mkalen:  To minimize memory footprint we remember only the OJB profile key
 96  
             //          (instead of all active class-mappings).
 97  
             final Object key = mm.getCurrentProfileKey();
 98  
             if (key == null)
 99  
             {
 100  
                 // mkalen:  Unsupported: using proxies with per-thread metadata changes without profile keys.
 101  
                 throw new MetadataException("Trying to create a Collection proxy with per-thread metadata changes enabled, but no profile key.");
 102  
             }
 103  
             setProfileKey(key);
 104  
         }
 105  
         setBrokerKey(brokerKey);
 106  
         setCollectionClass(collClass);
 107  
         setQuery(query);
 108  
     }
 109  
 
 110  
     /**
 111  
      * Reactivates metadata profile used when creating proxy, if needed.
 112  
      * Calls to this method should be guarded by checking
 113  
      * {@link #_perThreadDescriptorsEnabled} since the profile never
 114  
      * needs to be reloaded if not using pre-thread metadata changes.
 115  
      */
 116  
     protected void loadProfileIfNeeded()
 117  
     {
 118  
         final Object key = getProfileKey();
 119  
         if (key != null)
 120  
         {
 121  
             final MetadataManager mm = MetadataManager.getInstance();
 122  
             if (!key.equals(mm.getCurrentProfileKey()))
 123  
             {
 124  
                 mm.loadProfile(key);
 125  
             }
 126  
         }
 127  
     }
 128  
 
 129  
     /**
 130  
      * Determines whether the collection data already has been loaded from the database.
 131  
      *
 132  
      * @return <code>true</code> if the data is already loaded
 133  
      */
 134  
     public boolean isLoaded()
 135  
     {
 136  
         return _data != null;
 137  
     }
 138  
 
 139  
     /**
 140  
      * Determines the number of elements that the query would return. Override this
 141  
      * method if the size shall be determined in a specific way.
 142  
      * 
 143  
      * @return The number of elements
 144  
      */
 145  
     protected synchronized int loadSize() throws PersistenceBrokerException
 146  
     {
 147  
         PersistenceBroker broker = getBroker();
 148  
         try
 149  
         {
 150  
             return broker.getCount(getQuery());
 151  
         }
 152  
         catch (Exception ex)
 153  
         {
 154  
             throw new PersistenceBrokerException(ex);
 155  
         }
 156  
         finally
 157  
         {
 158  
             releaseBroker(broker);
 159  
         }
 160  
     }
 161  
 
 162  
     /**
 163  
      * Sets the size internally.
 164  
      * 
 165  
      * @param size The new size
 166  
      */
 167  
     protected synchronized void setSize(int size)
 168  
     {
 169  
         _size = size;
 170  
     }
 171  
     
 172  
     /**
 173  
      * Loads the data from the database. Override this method if the objects
 174  
      * shall be loaded in a specific way.
 175  
      * 
 176  
      * @return The loaded data
 177  
      */
 178  
     protected Collection loadData() throws PersistenceBrokerException
 179  
     {
 180  
         PersistenceBroker broker = getBroker();
 181  
         try
 182  
         {
 183  
             Collection result;
 184  
 
 185  
             if (_data != null) // could be set by listener
 186  
             {
 187  
                 result = _data;
 188  
             }
 189  
             else if (_size != 0)
 190  
             {
 191  
                 // TODO: returned ManageableCollection should extend Collection to avoid
 192  
                 // this cast
 193  
                 result = (Collection) broker.getCollectionByQuery(getCollectionClass(), getQuery());
 194  
             }
 195  
             else
 196  
             {
 197  
                 result = (Collection)getCollectionClass().newInstance();
 198  
             }
 199  
             return result;
 200  
         }
 201  
         catch (Exception ex)
 202  
         {
 203  
             throw new PersistenceBrokerException(ex);
 204  
         }
 205  
         finally
 206  
         {
 207  
             releaseBroker(broker);
 208  
         }
 209  
     }
 210  
 
 211  
     /**
 212  
      * Notifies all listeners that the data is about to be loaded.
 213  
      */
 214  
     protected void beforeLoading()
 215  
     {
 216  
         if (_listeners != null)
 217  
         {
 218  
             CollectionProxyListener listener;
 219  
 
 220  
             if (_perThreadDescriptorsEnabled) {
 221  
                 loadProfileIfNeeded();
 222  
             }
 223  
             for (int idx = _listeners.size() - 1; idx >= 0; idx--)
 224  
             {
 225  
                 listener = (CollectionProxyListener)_listeners.get(idx);
 226  
                 listener.beforeLoading(this);
 227  
             }
 228  
         }
 229  
     }
 230  
 
 231  
     /**
 232  
      * Notifies all listeners that the data has been loaded.
 233  
      */
 234  
     protected void afterLoading()
 235  
     {
 236  
         if (_listeners != null)
 237  
         {
 238  
             CollectionProxyListener listener;
 239  
 
 240  
             if (_perThreadDescriptorsEnabled) {
 241  
                 loadProfileIfNeeded();
 242  
             }
 243  
             for (int idx = _listeners.size() - 1; idx >= 0; idx--)
 244  
             {
 245  
                 listener = (CollectionProxyListener)_listeners.get(idx);
 246  
                 listener.afterLoading(this);
 247  
             }
 248  
         }
 249  
     }
 250  
 
 251  
     /**
 252  
      * @see Collection#size()
 253  
      */
 254  
     public int size()
 255  
     {
 256  
         if (isLoaded())
 257  
         {
 258  
             return getData().size();
 259  
         }
 260  
         else
 261  
         {
 262  
             if (_size < 0)
 263  
             {
 264  
                 _size = loadSize();
 265  
             }
 266  
             return _size;
 267  
         }
 268  
     }
 269  
 
 270  
     /**
 271  
      * @see Collection#isEmpty()
 272  
      */
 273  
     public boolean isEmpty()
 274  
     {
 275  
         return size() == 0;
 276  
     }
 277  
 
 278  
     /**
 279  
      * @see Collection#contains(Object)
 280  
      */
 281  
     public boolean contains(Object o)
 282  
     {
 283  
         return getData().contains(o);
 284  
     }
 285  
 
 286  
     /**
 287  
      * @see Collection#iterator()
 288  
      */
 289  
     public Iterator iterator()
 290  
     {
 291  
         return getData().iterator();
 292  
     }
 293  
 
 294  
     /**
 295  
      * @see Collection#toArray()
 296  
      */
 297  
     public Object[] toArray()
 298  
     {
 299  
         return getData().toArray();
 300  
     }
 301  
 
 302  
     /**
 303  
      * @see Collection#toArray(Object[])
 304  
      */
 305  
     public Object[] toArray(Object[] a)
 306  
     {
 307  
         return getData().toArray(a);
 308  
     }
 309  
 
 310  
     /**
 311  
      * @see Collection#add(Object)
 312  
      */
 313  
     public boolean add(Object o)
 314  
     {
 315  
         return getData().add(o);
 316  
     }
 317  
 
 318  
     /**
 319  
      * @see Collection#remove(Object)
 320  
      */
 321  
     public boolean remove(Object o)
 322  
     {
 323  
         return getData().remove(o);
 324  
     }
 325  
 
 326  
     /**
 327  
      * @see Collection#containsAll(Collection)
 328  
      */
 329  
     public boolean containsAll(Collection c)
 330  
     {
 331  
         return getData().containsAll(c);
 332  
     }
 333  
 
 334  
     /**
 335  
      * @see Collection#addAll(Collection)
 336  
      */
 337  
     public boolean addAll(Collection c)
 338  
     {
 339  
         return getData().addAll(c);
 340  
     }
 341  
 
 342  
     /**
 343  
      * @see Collection#removeAll(Collection)
 344  
      */
 345  
     public boolean removeAll(Collection c)
 346  
     {
 347  
         return getData().removeAll(c);
 348  
     }
 349  
 
 350  
     /**
 351  
      * @see Collection#retainAll(Collection)
 352  
      */
 353  
     public boolean retainAll(Collection c)
 354  
     {
 355  
         return getData().retainAll(c);
 356  
     }
 357  
 
 358  
     /**
 359  
      * Clears the proxy. A cleared proxy is defined as loaded
 360  
      *
 361  
      * @see Collection#clear()
 362  
      */
 363  
     public void clear()
 364  
     {
 365  
         Class collClass = getCollectionClass();
 366  
 
 367  
         // ECER: assure we notify all objects being removed, 
 368  
         // necessary for RemovalAwareCollections...
 369  
         if (IRemovalAwareCollection.class.isAssignableFrom(collClass))
 370  
         {
 371  
             getData().clear();
 372  
         }
 373  
         else
 374  
         {
 375  
             Collection coll;
 376  
             // BRJ: use an empty collection so isLoaded will return true
 377  
             // for non RemovalAwareCollections only !! 
 378  
             try
 379  
             {
 380  
                 coll = (Collection) collClass.newInstance();
 381  
             }
 382  
             catch (Exception e)
 383  
             {
 384  
                 coll = new ArrayList();
 385  
             }
 386  
 
 387  
             setData(coll);
 388  
         }
 389  
         _size = 0;
 390  
     }
 391  
 
 392  
     /**
 393  
      * Returns the defining query.
 394  
      * 
 395  
      * @return The query
 396  
      */
 397  
     public Query getQuery()
 398  
     {
 399  
         return _query;
 400  
     }
 401  
 
 402  
     /**
 403  
      * Sets the defining query.
 404  
      * 
 405  
      * @param query The query
 406  
      */
 407  
     protected void setQuery(Query query)
 408  
     {
 409  
         _query = query;
 410  
     }
 411  
 
 412  
     /**
 413  
      * Release the broker instance.
 414  
      */
 415  
     protected synchronized void releaseBroker(PersistenceBroker broker)
 416  
     {
 417  
         /*
 418  
         arminw:
 419  
         only close the broker instance if we get
 420  
         it from the PBF, do nothing if we obtain it from
 421  
         PBThreadMapping
 422  
         */
 423  
         if (broker != null && _needsClose)
 424  
         {
 425  
             _needsClose = false;
 426  
             broker.close();
 427  
         }
 428  
     }
 429  
 
 430  
     /**
 431  
      * Acquires a broker instance. If no PBKey is available a runtime exception will be thrown.
 432  
      * 
 433  
      * @return A broker instance
 434  
      */
 435  
     protected synchronized PersistenceBroker getBroker() throws PBFactoryException
 436  
     {
 437  
         /*
 438  
             mkalen:
 439  
             NB! The loadProfileIfNeeded must be called _before_ acquiring a broker below,
 440  
             since some methods in PersistenceBrokerImpl will keep a local reference to
 441  
             the descriptor repository that was active during broker construction/refresh
 442  
             (not checking the repository beeing used on method invocation).
 443  
 
 444  
             PersistenceBrokerImpl#getClassDescriptor(Class clazz) is such a method,
 445  
             that will throw ClassNotPersistenceCapableException on the following scenario:
 446  
 
 447  
             (All happens in one thread only):
 448  
             t0: activate per-thread metadata changes
 449  
             t1: load, register and activate profile A
 450  
             t2: load object O1 witch collection proxy C to objects {O2} (C stores profile key K(A))
 451  
             t3: close broker from t2
 452  
             t4: load, register and activate profile B
 453  
             t5: reference O1.getO2Collection, causing C loadData() to be invoked
 454  
             t6: C calls getBroker
 455  
                 broker B is created and descriptorRepository is set to descriptors from profile B
 456  
             t7: C calls loadProfileIfNeeded, re-activating profile A
 457  
             t8: C calls B.getCollectionByQuery
 458  
             t9: B gets callback (via QueryReferenceBroker) to getClassDescriptor
 459  
                 the local descriptorRepository from t6 is used!
 460  
                 => We will now try to query for {O2} with profile B
 461  
                     (even though we re-activated profile A in t7)
 462  
                     => ClassNotPersistenceCapableException
 463  
 
 464  
             Keeping loadProfileIfNeeded() at the start of this method changes everything from t6:
 465  
             t6: C calls loadProfileIfNeeded, re-activating profile A
 466  
             t7: C calls getBroker,
 467  
                 broker B is created and descriptorRepository is set to descriptors from profile A
 468  
             t8: C calls B.getCollectionByQuery
 469  
             t9: B gets callback to getClassDescriptor,
 470  
                 the local descriptorRepository from t6 is used
 471  
                 => We query for {O2} with profile A
 472  
                     => All good :-)
 473  
         */
 474  
         if (_perThreadDescriptorsEnabled)
 475  
         {
 476  
             loadProfileIfNeeded();
 477  
         }
 478  
 
 479  
         PersistenceBroker broker;
 480  
         if (getBrokerKey() == null)
 481  
         {
 482  
             /*
 483  
             arminw:
 484  
             if no PBKey is set we throw an exception, because we don't
 485  
             know which PB (connection) should be used.
 486  
             */
 487  
             throw new OJBRuntimeException("Can't find associated PBKey. Need PBKey to obtain a valid" +
 488  
                                           "PersistenceBroker instance from intern resources.");
 489  
         }
 490  
         // first try to use the current threaded broker to avoid blocking
 491  
         broker = PersistenceBrokerThreadMapping.currentPersistenceBroker(getBrokerKey());
 492  
         // current broker not found or was closed, create a intern new one
 493  
         if (broker == null || broker.isClosed())
 494  
         {
 495  
             broker = PersistenceBrokerFactory.createPersistenceBroker(getBrokerKey());
 496  
             // signal that we use a new internal obtained PB instance to read the
 497  
             // data and that this instance have to be closed after use
 498  
             _needsClose = true;
 499  
         }
 500  
         return broker;
 501  
     }
 502  
 
 503  
     /**
 504  
      * Returns the collection data, load it if not already done so.
 505  
      * 
 506  
      * @return The data
 507  
      */
 508  
     public synchronized Collection getData()
 509  
     {
 510  
         if (!isLoaded())
 511  
         {
 512  
             beforeLoading();
 513  
             setData(loadData());
 514  
             afterLoading();
 515  
         }
 516  
         return _data;
 517  
     }
 518  
 
 519  
     /**
 520  
      * Sets the collection data.
 521  
      * 
 522  
      * @param data The data
 523  
      */
 524  
     public void setData(Collection data)
 525  
     {
 526  
         _data = data;
 527  
     }
 528  
 
 529  
     /**
 530  
      * Returns the collection type.
 531  
      * 
 532  
      * @return The collection type
 533  
      */
 534  
     public Class getCollectionClass()
 535  
     {
 536  
         return _collectionClass;
 537  
     }
 538  
 
 539  
     /**
 540  
      * Sets the collection type.
 541  
      * 
 542  
      * @param collClass The collection type
 543  
      */
 544  
     protected void setCollectionClass(Class collClass)
 545  
     {
 546  
         _collectionClass = collClass;
 547  
     }
 548  
 
 549  
     /**
 550  
      * @see org.apache.ojb.broker.ManageableCollection#ojbAdd(Object)
 551  
      */
 552  
     public void ojbAdd(Object anObject)
 553  
     {
 554  
         add(anObject);
 555  
     }
 556  
 
 557  
     /**
 558  
      * @see org.apache.ojb.broker.ManageableCollection#ojbAddAll(ManageableCollection)
 559  
      */
 560  
     public void ojbAddAll(ManageableCollection otherCollection)
 561  
     {
 562  
         addAll((CollectionProxyDefaultImpl)otherCollection);
 563  
     }
 564  
 
 565  
     /**
 566  
      * @see org.apache.ojb.broker.ManageableCollection#ojbIterator()
 567  
      */
 568  
     public Iterator ojbIterator()
 569  
     {
 570  
         return iterator();
 571  
     }
 572  
 
 573  
     /**
 574  
      * @see org.apache.ojb.broker.ManageableCollection#afterStore(PersistenceBroker broker)
 575  
      */
 576  
     public void afterStore(PersistenceBroker broker) throws PersistenceBrokerException
 577  
     {
 578  
         // If the real subject is a ManageableCollection
 579  
         // the afterStore() callback must be invoked !
 580  
         Collection c = getData();
 581  
 
 582  
         if (c instanceof ManageableCollection)
 583  
         {
 584  
             ((ManageableCollection)c).afterStore(broker);
 585  
         }
 586  
     }
 587  
 
 588  
     /**
 589  
      * Returns the key of the persistence broker used by this collection.
 590  
      * 
 591  
      * @return The broker key
 592  
      */
 593  
     public PBKey getBrokerKey()
 594  
     {
 595  
         return _brokerKey;
 596  
     }
 597  
 
 598  
     /**
 599  
      * Sets the key of the persistence broker used by this collection.
 600  
      * 
 601  
      * @param brokerKey The key of the broker
 602  
      */
 603  
     protected void setBrokerKey(PBKey brokerKey)
 604  
     {
 605  
         _brokerKey = brokerKey;
 606  
     }
 607  
 
 608  
     /**
 609  
      * Returns the metadata profile key used when creating this proxy.
 610  
      *
 611  
      * @return brokerKey The key of the broker
 612  
      */
 613  
     protected Object getProfileKey()
 614  
     {
 615  
         return _profileKey;
 616  
     }
 617  
 
 618  
     /**
 619  
      * Sets the metadata profile key used when creating this proxy.
 620  
      *
 621  
      * @param profileKey the metadata profile key
 622  
      */
 623  
     public void setProfileKey(Object profileKey)
 624  
     {
 625  
         _profileKey = profileKey;
 626  
     }
 627  
 
 628  
     /**
 629  
      * Adds a listener to this collection.
 630  
      * 
 631  
      * @param listener The listener to add
 632  
      */
 633  
     public synchronized void addListener(CollectionProxyListener listener)
 634  
     {
 635  
         if (_listeners == null)
 636  
         {
 637  
             _listeners = new ArrayList();
 638  
         }
 639  
         // to avoid multi-add of same listener, do check
 640  
         if(!_listeners.contains(listener))
 641  
         {
 642  
             _listeners.add(listener);
 643  
         }
 644  
     }
 645  
 
 646  
     /**
 647  
      * Removes the given listener from this collecton.
 648  
      * 
 649  
      * @param listener The listener to remove
 650  
      */
 651  
     public synchronized void removeListener(CollectionProxyListener listener)
 652  
     {
 653  
         if (_listeners != null)
 654  
         {
 655  
             _listeners.remove(listener);
 656  
         }
 657  
     }
 658  
 
 659  
 }