001 package org.apache.ojb.broker.core.proxy;
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.util.ArrayList;
019 import java.util.Collection;
020 import java.util.Iterator;
021
022 import org.apache.ojb.broker.ManageableCollection;
023 import org.apache.ojb.broker.OJBRuntimeException;
024 import org.apache.ojb.broker.PBFactoryException;
025 import org.apache.ojb.broker.PBKey;
026 import org.apache.ojb.broker.PersistenceBroker;
027 import org.apache.ojb.broker.PersistenceBrokerException;
028 import org.apache.ojb.broker.PersistenceBrokerFactory;
029 import org.apache.ojb.broker.metadata.MetadataManager;
030 import org.apache.ojb.broker.metadata.MetadataException;
031 import org.apache.ojb.broker.core.PersistenceBrokerThreadMapping;
032 import org.apache.ojb.broker.query.Query;
033 import org.apache.ojb.broker.util.collections.IRemovalAwareCollection;
034 import org.apache.ojb.broker.util.collections.RemovalAwareCollection;
035
036 /**
037 * A place holder for a whole collection to support deferred loading of relationships.
038 * The complete collection is loaded on access to the data.
039 *
040 * @author <a href="mailto:jbraeuchi@hotmail.com">Jakob Braeuchi</a>
041 * @version $Id: CollectionProxyDefaultImpl.java,v 1.1 2007-08-24 22:17:32 ewestfal Exp $
042 */
043 public class CollectionProxyDefaultImpl implements Collection, ManageableCollection, CollectionProxy
044 {
045 /** The key for acquiring the above broker */
046 private PBKey _brokerKey;
047 /** Flag set when per-thread metadata profiles are in use. */
048 private boolean _perThreadDescriptorsEnabled;
049 /** Profile key used when lazy-loading with per-thread metadata profiles. */
050 private Object _profileKey;
051 /** The query that defines the values in the collection */
052 private Query _query;
053 /** The actual data (if already loaded) */
054 private Collection _data;
055 /** The collection type */
056 private Class _collectionClass;
057 /** The number of objects */
058 private int _size = -1;
059 /*
060 arminw
061 fix a bug, caused by closing PB instances
062 obtained from PersistenceBrokerThreadMapping.
063 TODO: Could we find a better solution for this?
064 */
065 private boolean _needsClose;
066 /** Objects that listen on this proxy for loading events */
067 private transient ArrayList _listeners;
068
069 /**
070 * Creates a new collection proxy (uses
071 * {@link org.apache.ojb.broker.util.collections.RemovalAwareCollection}
072 * as the collection class).
073 *
074 * @param brokerKey The key of the persistence broker
075 * @param query The defining query
076 */
077 public CollectionProxyDefaultImpl(PBKey brokerKey, Query query)
078 {
079 this(brokerKey, RemovalAwareCollection.class, query);
080 }
081
082 /**
083 * Creates a new collection proxy that uses the given collection type.
084 *
085 * @param brokerKey The key of the persistence broker
086 * @param collClass The collection type
087 * @param query The defining query
088 */
089 public CollectionProxyDefaultImpl(PBKey brokerKey, Class collClass, Query query)
090 {
091 MetadataManager mm = MetadataManager.getInstance();
092 _perThreadDescriptorsEnabled = mm.isEnablePerThreadChanges();
093 if (_perThreadDescriptorsEnabled)
094 {
095 // mkalen: To minimize memory footprint we remember only the OJB profile key
096 // (instead of all active class-mappings).
097 final Object key = mm.getCurrentProfileKey();
098 if (key == null)
099 {
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 }