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    }