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.7.2.7 2005/12/21 22:25:30 tomdz Exp $
042: */
043: public class CollectionProxyDefaultImpl implements Collection,
044: ManageableCollection, CollectionProxy {
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: this (brokerKey, RemovalAwareCollection.class, query);
079: }
080:
081: /**
082: * Creates a new collection proxy that uses the given collection type.
083: *
084: * @param brokerKey The key of the persistence broker
085: * @param collClass The collection type
086: * @param query The defining query
087: */
088: public CollectionProxyDefaultImpl(PBKey brokerKey, Class collClass,
089: Query query) {
090: MetadataManager mm = MetadataManager.getInstance();
091: _perThreadDescriptorsEnabled = mm.isEnablePerThreadChanges();
092: if (_perThreadDescriptorsEnabled) {
093: // mkalen: To minimize memory footprint we remember only the OJB profile key
094: // (instead of all active class-mappings).
095: final Object key = mm.getCurrentProfileKey();
096: if (key == null) {
097: // mkalen: Unsupported: using proxies with per-thread metadata changes without profile keys.
098: throw new MetadataException(
099: "Trying to create a Collection proxy with per-thread metadata changes enabled, but no profile key.");
100: }
101: setProfileKey(key);
102: }
103: setBrokerKey(brokerKey);
104: setCollectionClass(collClass);
105: setQuery(query);
106: }
107:
108: /**
109: * Reactivates metadata profile used when creating proxy, if needed.
110: * Calls to this method should be guarded by checking
111: * {@link #_perThreadDescriptorsEnabled} since the profile never
112: * needs to be reloaded if not using pre-thread metadata changes.
113: */
114: protected void loadProfileIfNeeded() {
115: final Object key = getProfileKey();
116: if (key != null) {
117: final MetadataManager mm = MetadataManager.getInstance();
118: if (!key.equals(mm.getCurrentProfileKey())) {
119: mm.loadProfile(key);
120: }
121: }
122: }
123:
124: /**
125: * Determines whether the collection data already has been loaded from the database.
126: *
127: * @return <code>true</code> if the data is already loaded
128: */
129: public boolean isLoaded() {
130: return _data != null;
131: }
132:
133: /**
134: * Determines the number of elements that the query would return. Override this
135: * method if the size shall be determined in a specific way.
136: *
137: * @return The number of elements
138: */
139: protected synchronized int loadSize()
140: throws PersistenceBrokerException {
141: PersistenceBroker broker = getBroker();
142: try {
143: return broker.getCount(getQuery());
144: } catch (Exception ex) {
145: throw new PersistenceBrokerException(ex);
146: } finally {
147: releaseBroker(broker);
148: }
149: }
150:
151: /**
152: * Sets the size internally.
153: *
154: * @param size The new size
155: */
156: protected synchronized void setSize(int size) {
157: _size = size;
158: }
159:
160: /**
161: * Loads the data from the database. Override this method if the objects
162: * shall be loaded in a specific way.
163: *
164: * @return The loaded data
165: */
166: protected Collection loadData() throws PersistenceBrokerException {
167: PersistenceBroker broker = getBroker();
168: try {
169: Collection result;
170:
171: if (_data != null) // could be set by listener
172: {
173: result = _data;
174: } else if (_size != 0) {
175: // TODO: returned ManageableCollection should extend Collection to avoid
176: // this cast
177: result = (Collection) broker.getCollectionByQuery(
178: getCollectionClass(), getQuery());
179: } else {
180: result = (Collection) getCollectionClass()
181: .newInstance();
182: }
183: return result;
184: } catch (Exception ex) {
185: throw new PersistenceBrokerException(ex);
186: } finally {
187: releaseBroker(broker);
188: }
189: }
190:
191: /**
192: * Notifies all listeners that the data is about to be loaded.
193: */
194: protected void beforeLoading() {
195: if (_listeners != null) {
196: CollectionProxyListener listener;
197:
198: if (_perThreadDescriptorsEnabled) {
199: loadProfileIfNeeded();
200: }
201: for (int idx = _listeners.size() - 1; idx >= 0; idx--) {
202: listener = (CollectionProxyListener) _listeners
203: .get(idx);
204: listener.beforeLoading(this );
205: }
206: }
207: }
208:
209: /**
210: * Notifies all listeners that the data has been loaded.
211: */
212: protected void afterLoading() {
213: if (_listeners != null) {
214: CollectionProxyListener listener;
215:
216: if (_perThreadDescriptorsEnabled) {
217: loadProfileIfNeeded();
218: }
219: for (int idx = _listeners.size() - 1; idx >= 0; idx--) {
220: listener = (CollectionProxyListener) _listeners
221: .get(idx);
222: listener.afterLoading(this );
223: }
224: }
225: }
226:
227: /**
228: * @see Collection#size()
229: */
230: public int size() {
231: if (isLoaded()) {
232: return getData().size();
233: } else {
234: if (_size < 0) {
235: _size = loadSize();
236: }
237: return _size;
238: }
239: }
240:
241: /**
242: * @see Collection#isEmpty()
243: */
244: public boolean isEmpty() {
245: return size() == 0;
246: }
247:
248: /**
249: * @see Collection#contains(Object)
250: */
251: public boolean contains(Object o) {
252: return getData().contains(o);
253: }
254:
255: /**
256: * @see Collection#iterator()
257: */
258: public Iterator iterator() {
259: return getData().iterator();
260: }
261:
262: /**
263: * @see Collection#toArray()
264: */
265: public Object[] toArray() {
266: return getData().toArray();
267: }
268:
269: /**
270: * @see Collection#toArray(Object[])
271: */
272: public Object[] toArray(Object[] a) {
273: return getData().toArray(a);
274: }
275:
276: /**
277: * @see Collection#add(Object)
278: */
279: public boolean add(Object o) {
280: return getData().add(o);
281: }
282:
283: /**
284: * @see Collection#remove(Object)
285: */
286: public boolean remove(Object o) {
287: return getData().remove(o);
288: }
289:
290: /**
291: * @see Collection#containsAll(Collection)
292: */
293: public boolean containsAll(Collection c) {
294: return getData().containsAll(c);
295: }
296:
297: /**
298: * @see Collection#addAll(Collection)
299: */
300: public boolean addAll(Collection c) {
301: return getData().addAll(c);
302: }
303:
304: /**
305: * @see Collection#removeAll(Collection)
306: */
307: public boolean removeAll(Collection c) {
308: return getData().removeAll(c);
309: }
310:
311: /**
312: * @see Collection#retainAll(Collection)
313: */
314: public boolean retainAll(Collection c) {
315: return getData().retainAll(c);
316: }
317:
318: /**
319: * Clears the proxy. A cleared proxy is defined as loaded
320: *
321: * @see Collection#clear()
322: */
323: public void clear() {
324: Class collClass = getCollectionClass();
325:
326: // ECER: assure we notify all objects being removed,
327: // necessary for RemovalAwareCollections...
328: if (IRemovalAwareCollection.class.isAssignableFrom(collClass)) {
329: getData().clear();
330: } else {
331: Collection coll;
332: // BRJ: use an empty collection so isLoaded will return true
333: // for non RemovalAwareCollections only !!
334: try {
335: coll = (Collection) collClass.newInstance();
336: } catch (Exception e) {
337: coll = new ArrayList();
338: }
339:
340: setData(coll);
341: }
342: _size = 0;
343: }
344:
345: /**
346: * Returns the defining query.
347: *
348: * @return The query
349: */
350: public Query getQuery() {
351: return _query;
352: }
353:
354: /**
355: * Sets the defining query.
356: *
357: * @param query The query
358: */
359: protected void setQuery(Query query) {
360: _query = query;
361: }
362:
363: /**
364: * Release the broker instance.
365: */
366: protected synchronized void releaseBroker(PersistenceBroker broker) {
367: /*
368: arminw:
369: only close the broker instance if we get
370: it from the PBF, do nothing if we obtain it from
371: PBThreadMapping
372: */
373: if (broker != null && _needsClose) {
374: _needsClose = false;
375: broker.close();
376: }
377: }
378:
379: /**
380: * Acquires a broker instance. If no PBKey is available a runtime exception will be thrown.
381: *
382: * @return A broker instance
383: */
384: protected synchronized PersistenceBroker getBroker()
385: throws PBFactoryException {
386: /*
387: mkalen:
388: NB! The loadProfileIfNeeded must be called _before_ acquiring a broker below,
389: since some methods in PersistenceBrokerImpl will keep a local reference to
390: the descriptor repository that was active during broker construction/refresh
391: (not checking the repository beeing used on method invocation).
392:
393: PersistenceBrokerImpl#getClassDescriptor(Class clazz) is such a method,
394: that will throw ClassNotPersistenceCapableException on the following scenario:
395:
396: (All happens in one thread only):
397: t0: activate per-thread metadata changes
398: t1: load, register and activate profile A
399: t2: load object O1 witch collection proxy C to objects {O2} (C stores profile key K(A))
400: t3: close broker from t2
401: t4: load, register and activate profile B
402: t5: reference O1.getO2Collection, causing C loadData() to be invoked
403: t6: C calls getBroker
404: broker B is created and descriptorRepository is set to descriptors from profile B
405: t7: C calls loadProfileIfNeeded, re-activating profile A
406: t8: C calls B.getCollectionByQuery
407: t9: B gets callback (via QueryReferenceBroker) to getClassDescriptor
408: the local descriptorRepository from t6 is used!
409: => We will now try to query for {O2} with profile B
410: (even though we re-activated profile A in t7)
411: => ClassNotPersistenceCapableException
412:
413: Keeping loadProfileIfNeeded() at the start of this method changes everything from t6:
414: t6: C calls loadProfileIfNeeded, re-activating profile A
415: t7: C calls getBroker,
416: broker B is created and descriptorRepository is set to descriptors from profile A
417: t8: C calls B.getCollectionByQuery
418: t9: B gets callback to getClassDescriptor,
419: the local descriptorRepository from t6 is used
420: => We query for {O2} with profile A
421: => All good :-)
422: */
423: if (_perThreadDescriptorsEnabled) {
424: loadProfileIfNeeded();
425: }
426:
427: PersistenceBroker broker;
428: if (getBrokerKey() == null) {
429: /*
430: arminw:
431: if no PBKey is set we throw an exception, because we don't
432: know which PB (connection) should be used.
433: */
434: throw new OJBRuntimeException(
435: "Can't find associated PBKey. Need PBKey to obtain a valid"
436: + "PersistenceBroker instance from intern resources.");
437: }
438: // first try to use the current threaded broker to avoid blocking
439: broker = PersistenceBrokerThreadMapping
440: .currentPersistenceBroker(getBrokerKey());
441: // current broker not found or was closed, create a intern new one
442: if (broker == null || broker.isClosed()) {
443: broker = PersistenceBrokerFactory
444: .createPersistenceBroker(getBrokerKey());
445: // signal that we use a new internal obtained PB instance to read the
446: // data and that this instance have to be closed after use
447: _needsClose = true;
448: }
449: return broker;
450: }
451:
452: /**
453: * Returns the collection data, load it if not already done so.
454: *
455: * @return The data
456: */
457: public synchronized Collection getData() {
458: if (!isLoaded()) {
459: beforeLoading();
460: setData(loadData());
461: afterLoading();
462: }
463: return _data;
464: }
465:
466: /**
467: * Sets the collection data.
468: *
469: * @param data The data
470: */
471: public void setData(Collection data) {
472: _data = data;
473: }
474:
475: /**
476: * Returns the collection type.
477: *
478: * @return The collection type
479: */
480: public Class getCollectionClass() {
481: return _collectionClass;
482: }
483:
484: /**
485: * Sets the collection type.
486: *
487: * @param collClass The collection type
488: */
489: protected void setCollectionClass(Class collClass) {
490: _collectionClass = collClass;
491: }
492:
493: /**
494: * @see org.apache.ojb.broker.ManageableCollection#ojbAdd(Object)
495: */
496: public void ojbAdd(Object anObject) {
497: add(anObject);
498: }
499:
500: /**
501: * @see org.apache.ojb.broker.ManageableCollection#ojbAddAll(ManageableCollection)
502: */
503: public void ojbAddAll(ManageableCollection otherCollection) {
504: addAll((CollectionProxyDefaultImpl) otherCollection);
505: }
506:
507: /**
508: * @see org.apache.ojb.broker.ManageableCollection#ojbIterator()
509: */
510: public Iterator ojbIterator() {
511: return iterator();
512: }
513:
514: /**
515: * @see org.apache.ojb.broker.ManageableCollection#afterStore(PersistenceBroker broker)
516: */
517: public void afterStore(PersistenceBroker broker)
518: throws PersistenceBrokerException {
519: // If the real subject is a ManageableCollection
520: // the afterStore() callback must be invoked !
521: Collection c = getData();
522:
523: if (c instanceof ManageableCollection) {
524: ((ManageableCollection) c).afterStore(broker);
525: }
526: }
527:
528: /**
529: * Returns the key of the persistence broker used by this collection.
530: *
531: * @return The broker key
532: */
533: public PBKey getBrokerKey() {
534: return _brokerKey;
535: }
536:
537: /**
538: * Sets the key of the persistence broker used by this collection.
539: *
540: * @param brokerKey The key of the broker
541: */
542: protected void setBrokerKey(PBKey brokerKey) {
543: _brokerKey = brokerKey;
544: }
545:
546: /**
547: * Returns the metadata profile key used when creating this proxy.
548: *
549: * @return brokerKey The key of the broker
550: */
551: protected Object getProfileKey() {
552: return _profileKey;
553: }
554:
555: /**
556: * Sets the metadata profile key used when creating this proxy.
557: *
558: * @param profileKey the metadata profile key
559: */
560: public void setProfileKey(Object profileKey) {
561: _profileKey = profileKey;
562: }
563:
564: /**
565: * Adds a listener to this collection.
566: *
567: * @param listener The listener to add
568: */
569: public synchronized void addListener(
570: CollectionProxyListener listener) {
571: if (_listeners == null) {
572: _listeners = new ArrayList();
573: }
574: // to avoid multi-add of same listener, do check
575: if (!_listeners.contains(listener)) {
576: _listeners.add(listener);
577: }
578: }
579:
580: /**
581: * Removes the given listener from this collecton.
582: *
583: * @param listener The listener to remove
584: */
585: public synchronized void removeListener(
586: CollectionProxyListener listener) {
587: if (_listeners != null) {
588: _listeners.remove(listener);
589: }
590: }
591:
592: }
|