001    package org.apache.ojb.broker.core;
002    
003    /* Copyright 2003-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.reflect.Array;
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.apache.ojb.broker.Identity;
027    import org.apache.ojb.broker.ManageableCollection;
028    import org.apache.ojb.broker.PBLifeCycleEvent;
029    import org.apache.ojb.broker.PersistenceBrokerException;
030    import org.apache.ojb.broker.accesslayer.OJBIterator;
031    import org.apache.ojb.broker.accesslayer.PagingIterator;
032    import org.apache.ojb.broker.accesslayer.PlainPrefetcher;
033    import org.apache.ojb.broker.accesslayer.RelationshipPrefetcher;
034    import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
035    import org.apache.ojb.broker.core.proxy.CollectionProxyListener;
036    import org.apache.ojb.broker.core.proxy.IndirectionHandler;
037    import org.apache.ojb.broker.core.proxy.MaterializationListener;
038    import org.apache.ojb.broker.core.proxy.ProxyHelper;
039    import org.apache.ojb.broker.metadata.ClassDescriptor;
040    import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
041    import org.apache.ojb.broker.metadata.CollectionDescriptor;
042    import org.apache.ojb.broker.metadata.FieldDescriptor;
043    import org.apache.ojb.broker.metadata.FieldHelper;
044    import org.apache.ojb.broker.metadata.MetadataException;
045    import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
046    import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
047    import org.apache.ojb.broker.query.Criteria;
048    import org.apache.ojb.broker.query.Query;
049    import org.apache.ojb.broker.query.QueryByCriteria;
050    import org.apache.ojb.broker.query.QueryFactory;
051    import org.apache.ojb.broker.util.BrokerHelper;
052    import org.apache.ojb.broker.util.collections.ManageableArrayList;
053    import org.apache.ojb.broker.util.collections.ManageableHashSet;
054    import org.apache.ojb.broker.util.collections.RemovalAwareCollection;
055    import org.apache.ojb.broker.util.collections.RemovalAwareList;
056    import org.apache.ojb.broker.util.collections.RemovalAwareSet;
057    import org.apache.ojb.broker.util.logging.Logger;
058    import org.apache.ojb.broker.util.logging.LoggerFactory;
059    
060    /**
061     * Encapsulates 1:1 and 1:n references and collection references stuff.
062     *
063     * TODO: Should we made this class independend from PB implementation class
064     * and only use PB interface methods?
065     *
066     * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
067     * @version $Id: QueryReferenceBroker.java,v 1.1 2007-08-24 22:17:35 ewestfal Exp $
068     */
069    public class QueryReferenceBroker
070    {
071        private Logger log = LoggerFactory.getLogger(QueryReferenceBroker.class);
072    
073        private PersistenceBrokerImpl pb;
074        private HashMap m_retrievalTasks;
075        private ArrayList prefetchingListeners;
076        private final boolean batchRetrieval = true;
077        private final boolean prefetchProxies = true;
078        private Class classToPrefetch;
079        private PBLifeCycleEvent afterLookupEvent;
080    
081        public QueryReferenceBroker(final PersistenceBrokerImpl pb)
082        {
083            this.pb = pb;
084            afterLookupEvent = new PBLifeCycleEvent(pb, PBLifeCycleEvent.Type.AFTER_LOOKUP);
085        }
086    
087        /**
088         * retrieve a collection of itemClass Objects matching the Query query
089         * @param collectionClass type the collection to be returned
090         * @param itemClass Class of item in collection
091         * @param query the query
092         */
093        private ManageableCollection getCollectionByQuery(Class collectionClass, Class itemClass, Query query)
094                throws ClassNotPersistenceCapableException, PersistenceBrokerException
095        {
096            if (log.isDebugEnabled()) log.debug("getCollectionByQuery (" + collectionClass + ", " + itemClass + ", " + query + ")");
097    
098            ClassDescriptor cld = pb.getClassDescriptor(itemClass);
099            ManageableCollection result = null;
100            OJBIterator iter = null;
101            int fullSize = -1;
102            int size = 0;
103    
104            final boolean isRetrievalTasksCreated = batchRetrieval && m_retrievalTasks == null;
105            if (isRetrievalTasksCreated)
106            {
107                // Maps ReferenceDescriptors to HashSets of owners
108                m_retrievalTasks = new HashMap();
109            }
110    
111            // ==> enable materialization cache
112            pb.getInternalCache().enableMaterializationCache();
113            try
114            {
115                result = (ManageableCollection) collectionClass.newInstance();
116                
117                // now iterate over all elements and add them to the new collection
118                // lifecycle events are disabled
119                iter = pb.getIteratorFromQuery(query, cld);
120                iter.disableLifeCycleEvents();
121    
122                // BRJ : get fullSizefor Query
123                // to be removed when Query.fullSize is removed
124                if (iter instanceof PagingIterator)
125                {
126                    fullSize = iter.fullSize();
127                }
128    
129                while (iter.hasNext())
130                {
131                    Object candidate = iter.next();
132    
133                    /**
134                     * MBAIRD
135                     * candidate CAN be null in the case of materializing from an iterator based
136                     * on a query for a class that is mapped to a table that has other classes
137                     * mapped to that table as well, but aren't extents.
138                     */
139                    if (candidate != null)
140                    {
141                        IndirectionHandler handler = ProxyHelper.getIndirectionHandler(candidate);
142    
143                        if ((handler != null) || itemClass.isAssignableFrom(candidate.getClass()))
144                        {
145                            result.ojbAdd(candidate);
146    
147                            // BRJ: count added objects
148                            // to be removed when Query.fullSize is removed
149                            size++;
150                        }
151                        else
152                        {
153                            //warn the user
154                            log.warn("Candidate object ["+candidate
155                                        +"] class ["+candidate.getClass().getName()
156                                        +"] is not a subtype of ["+itemClass.getName()
157                                        +"] or any type of proxy. NOT INCLUDED in result collection");
158                        }
159                        if (prefetchProxies && (handler != null)
160                                && (cld.getProxyPrefetchingLimit() > 0)
161                                && addRetrievalTask(candidate, this))
162                        {
163                            new PBMaterializationListener(candidate, m_retrievalTasks,
164                                    this, cld.getProxyPrefetchingLimit());
165                        }
166                    }
167                }
168    
169                if (isRetrievalTasksCreated)
170                {
171                    // turn off auto prefetching for related proxies
172                    final Class saveClassToPrefetch = classToPrefetch;
173                    classToPrefetch = null;
174                    try
175                    {
176                        performRetrievalTasks();
177                    }
178                    finally
179                    {
180                        classToPrefetch = saveClassToPrefetch;
181                    }
182                }
183    
184                // BRJ: fire LifeCycleEvents after execution of RetrievalTasks
185                // to ensure objects are fully materialized
186                Iterator resultIter = result.ojbIterator();
187                while (resultIter.hasNext())
188                {
189                    Object obj = resultIter.next();
190                    afterLookupEvent.setTarget(obj);
191                    pb.fireBrokerEvent(afterLookupEvent);
192                    afterLookupEvent.setTarget(null);
193                }
194    
195                // ==> disable materialization cache
196                pb.getInternalCache().disableMaterializationCache();
197            }
198            catch(RuntimeException e)
199            {
200                // ==> clear materialization cache
201                pb.getInternalCache().doLocalClear();
202                throw e;
203            }
204            catch (Exception ex)
205            {
206                // ==> clear materialization cache
207                pb.getInternalCache().doLocalClear();
208                log.error(ex);
209                throw new PersistenceBrokerException(ex);
210            }
211            finally
212            {
213                if (iter != null)
214                {
215                    iter.releaseDbResources();
216                }
217                if (isRetrievalTasksCreated)
218                {
219                    m_retrievalTasks = null;
220                }
221            }
222    
223            // BRJ: store fullSize in Query to re-enable deprecated functionality
224            // to be removed when Query.fullSize is removed
225            if (fullSize < 0)
226            {
227                fullSize = size;    // use size of result
228            }
229            query.fullSize(fullSize);
230            
231            return result;
232        }
233    
234        /**
235         * retrieve a collection of type collectionClass matching the Query query
236         * if lazy = true return a CollectionProxy
237         *
238         * @param collectionClass
239         * @param query
240         * @param lazy
241         * @return ManageableCollection
242         * @throws PersistenceBrokerException
243         */
244        public ManageableCollection getCollectionByQuery(Class collectionClass, Query query, boolean lazy) throws PersistenceBrokerException
245        {
246            ManageableCollection result;
247    
248            try
249            {
250                // BRJ: return empty Collection  for null query
251                if (query == null)
252                {
253                    result = (ManageableCollection)collectionClass.newInstance();
254                }
255                else
256                {
257                    if (lazy)
258                    {
259                        result = pb.getProxyFactory().createCollectionProxy(pb.getPBKey(), query, collectionClass);
260                    }
261                    else
262                    {
263                        result = getCollectionByQuery(collectionClass, query.getSearchClass(), query);
264                    }
265                }
266                return result;
267            }
268            catch (Exception e)
269            {
270                if(e instanceof PersistenceBrokerException)
271                {
272                    throw (PersistenceBrokerException) e;
273                }
274                else
275                {
276                    throw new PersistenceBrokerException(e);
277                }
278            }
279        }
280    
281        /**
282         * retrieve a collection of itemClass Objects matching the Query query
283         */
284        public Collection getCollectionByQuery(Query query, boolean lazy) throws PersistenceBrokerException
285        {
286            // thma: the following cast is safe because:
287            // 1. ManageableVector implements Collection (will be returned if lazy == false)
288            // 2. CollectionProxy implements Collection (will be returned if lazy == true)
289            return (Collection) getCollectionByQuery(RemovalAwareCollection.class, query, lazy);
290        }
291    
292        
293        private Class getCollectionTypeClass(CollectionDescriptor cds) throws PersistenceBrokerException{
294            // BRJ: do not use RemovalAwareCollection for m:n relationships
295            // see http://db.apache.org/ojb/docu/guides/basic-technique.html#Mapping+m%3An+associations
296    
297            Class fieldType = cds.getPersistentField().getType();
298            Class collType;
299    
300            if (fieldType.isArray() || fieldType.isAssignableFrom(RemovalAwareCollection.class))
301            {
302                collType = cds.isMtoNRelation() ? ManageableArrayList.class : RemovalAwareCollection.class;
303            }
304            else if (fieldType.isAssignableFrom(RemovalAwareList.class))
305            {
306                collType = cds.isMtoNRelation() ? ManageableArrayList.class : RemovalAwareList.class;
307            }
308            else if (fieldType.isAssignableFrom(RemovalAwareSet.class))
309            {
310                collType = cds.isMtoNRelation() ? ManageableHashSet.class : RemovalAwareSet.class;
311            }
312            else if (ManageableCollection.class.isAssignableFrom(fieldType))
313            {
314                collType = fieldType;
315            }
316            else
317            {
318                throw new MetadataException("Cannot determine a default collection type for collection "+cds.getAttributeName()+" in type "+cds.getClassDescriptor().getClassNameOfObject());
319            }
320            return collType;
321        }
322        
323        
324        /**
325         * @return true if this is the first task for the given ObjectReferenceDescriptor
326         */
327        private boolean addRetrievalTask(Object obj, Object key)
328        {
329            ArrayList owners = (ArrayList) m_retrievalTasks.get(key);
330            boolean isFirst = false;
331    
332            if (owners == null)
333            {
334                owners = new ArrayList();
335                m_retrievalTasks.put(key, owners);
336                isFirst = true;
337            }
338            owners.add(obj);
339            return isFirst;
340        }
341    
342        /**
343         * Perform the stored retrieval tasks
344         * BRJ: made it public to access it from BasePrefetcher
345         * TODO: this is a quick fix !
346         */
347        public void performRetrievalTasks()
348        {
349            if (m_retrievalTasks == null)
350            {
351                return;
352            }
353    
354            while (m_retrievalTasks.size() > 0)
355            {
356                HashMap tmp = m_retrievalTasks;
357                m_retrievalTasks = new HashMap();
358                // during execution of these tasks new tasks may be added
359    
360                for (Iterator it = tmp.entrySet().iterator(); it.hasNext(); )
361                {
362                    Map.Entry entry = (Map.Entry) it.next();
363                    Object key = entry.getKey();
364    
365                    if (!(key instanceof ObjectReferenceDescriptor))
366                    {
367                        continue;
368                    }
369    
370                    ObjectReferenceDescriptor ord = (ObjectReferenceDescriptor) key;
371                    RelationshipPrefetcher prefetcher;
372                    ArrayList owners = (ArrayList) entry.getValue();
373    
374    //                if (ord instanceof SuperReferenceDescriptor || ord.isLazy() || (ord.getItemProxyClass() != null))
375                    if (ord.isLazy() || (ord.getItemProxyClass() != null))
376                    {
377                        continue;
378                    }
379    
380                    prefetcher = pb.getRelationshipPrefetcherFactory().createRelationshipPrefetcher(ord);
381                    prefetcher.prefetchRelationship(owners);
382                    it.remove();
383                }
384            }
385        }
386    
387        /**
388         * Retrieve a single Reference.
389         * This implementation retrieves a referenced object from the data backend
390         * if <b>cascade-retrieve</b> is true or if <b>forced</b> is true.
391         *
392         * @param obj - object that will have it's field set with a referenced object.
393         * @param cld - the ClassDescriptor describring obj
394         * @param rds - the ObjectReferenceDescriptor of the reference attribute to be loaded
395         * @param forced - if set to true, the reference is loaded even if the rds differs.
396         */
397        public void retrieveReference(Object obj, ClassDescriptor cld, ObjectReferenceDescriptor rds, boolean forced)
398        {
399            PersistentField refField;
400            Object refObj = null;
401            
402            if (forced || rds.getCascadeRetrieve())
403            {
404                pb.getInternalCache().enableMaterializationCache();
405                try
406                {
407                    Identity id = getReferencedObjectIdentity(obj, rds, cld);
408                    boolean isRefObjDefined = true;
409    
410                    if (id == null)
411                    {
412                        refObj = null;
413                    } //JMM : why not see if the object has already been loaded
414                    else if ( pb.serviceObjectCache().lookup(id) != null )
415                    {
416                        refObj = pb.doGetObjectByIdentity(id);
417                        if (rds.isSuperReferenceDescriptor()) 
418                        {
419                            // walk the super-references
420                            ClassDescriptor superCld = cld.getRepository().getDescriptorFor(rds.getItemClass());
421                            retrieveReferences(refObj, superCld, false);
422                            retrieveCollections(refObj, superCld, false);                        
423                        }
424                    }
425                    else if ((m_retrievalTasks != null)
426                            && !rds.isLazy()
427                            && (rds.getItemProxyClass() == null))
428                    {
429                        addRetrievalTask(obj, rds);
430                        isRefObjDefined = false;
431                    }
432                    else
433                    {
434                        refObj = getReferencedObject(id, rds);
435                    }
436    
437                    if (isRefObjDefined)
438                    {
439                        refField = rds.getPersistentField();
440                        refField.set(obj, refObj);
441    
442                        if ((refObj != null) && prefetchProxies
443                                && (m_retrievalTasks != null)
444                                && (rds.getProxyPrefetchingLimit() > 0))
445                        {
446                            IndirectionHandler handler = ProxyHelper.getIndirectionHandler(refObj);
447    
448                            if ((handler != null)
449                                    && addRetrievalTask(obj, rds))
450                            {
451                                new PBMaterializationListener(obj, m_retrievalTasks,
452                                        rds, rds.getProxyPrefetchingLimit());
453                            }
454                        }
455                    }
456    
457                    pb.getInternalCache().disableMaterializationCache();
458                }
459                catch(RuntimeException e)
460                {
461                    pb.getInternalCache().doLocalClear();
462                    throw e;
463                }
464            }
465        }
466        
467        /**
468         * Retrieve a single Reference.
469         * This implementation retrieves a referenced object from the data backend
470         * if <b>cascade-retrieve</b> is true or if <b>forced</b> is true.
471         *
472         * @param obj - object that will have it's field set with a referenced object.
473         * @param cld - the ClassDescriptor describring obj
474         * @param rds - the ObjectReferenceDescriptor of the reference attribute to be loaded
475         * @param forced - if set to true, the reference is loaded even if the rds differs.
476         */
477        public void retrieveProxyReference(Object obj, ClassDescriptor cld, ObjectReferenceDescriptor rds, boolean forced)
478        {
479            PersistentField refField;
480            Object refObj = null;
481    
482                pb.getInternalCache().enableMaterializationCache();
483                try
484                {
485                    Identity id = getReferencedObjectIdentity(obj, rds, cld);
486                    if (id != null){
487                        refObj = pb.createProxy(rds.getItemClass(), id);
488                    }
489                    refField = rds.getPersistentField();
490                    refField.set(obj, refObj);
491    
492                    if ((refObj != null) && prefetchProxies
493                            && (m_retrievalTasks != null)
494                            && (rds.getProxyPrefetchingLimit() > 0))
495                    {
496                        IndirectionHandler handler = ProxyHelper.getIndirectionHandler(refObj);
497    
498                        if ((handler != null)
499                                && addRetrievalTask(obj, rds))
500                        {
501                            new PBMaterializationListener(obj, m_retrievalTasks,
502                                    rds, rds.getProxyPrefetchingLimit());
503                        }
504                    }
505                    
506    
507                    pb.getInternalCache().disableMaterializationCache();
508                }
509                catch(RuntimeException e)
510                {
511                    pb.getInternalCache().doLocalClear();
512                    throw e;
513                }
514            
515        }
516    
517        /**
518         * Retrieve all References
519         *
520         * @param newObj the instance to be loaded or refreshed
521         * @param cld the ClassDescriptor of the instance
522         * @param forced if set to true loading is forced even if cld differs.
523         */
524        public void retrieveReferences(Object newObj, ClassDescriptor cld, boolean forced) throws PersistenceBrokerException
525        {
526            Iterator i = cld.getObjectReferenceDescriptors().iterator();
527    
528            // turn off auto prefetching for related proxies
529            final Class saveClassToPrefetch = classToPrefetch;
530            classToPrefetch = null;
531    
532            pb.getInternalCache().enableMaterializationCache();
533            try
534            {
535                while (i.hasNext())
536                {
537                    ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) i.next();
538                    retrieveReference(newObj, cld, rds, forced);
539                }
540    
541                pb.getInternalCache().disableMaterializationCache();
542            }
543            catch(RuntimeException e)
544            {
545                pb.getInternalCache().doLocalClear();
546                throw e;
547            }
548            finally
549            {
550                classToPrefetch = saveClassToPrefetch;
551            }
552        }
553        
554        /**
555         * Retrieve all References
556         *
557         * @param newObj the instance to be loaded or refreshed
558         * @param cld the ClassDescriptor of the instance
559         * @param forced if set to true loading is forced even if cld differs.
560         */
561        public void retrieveProxyReferences(Object newObj, ClassDescriptor cld, boolean forced) throws PersistenceBrokerException
562        {
563            Iterator i = cld.getObjectReferenceDescriptors().iterator();
564    
565            // turn off auto prefetching for related proxies
566            final Class saveClassToPrefetch = classToPrefetch;
567            classToPrefetch = null;
568    
569            pb.getInternalCache().enableMaterializationCache();
570            try
571            {
572                while (i.hasNext())
573                {
574                    ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) i.next();
575                    retrieveProxyReference(newObj, cld, rds, forced);
576                }
577    
578                pb.getInternalCache().disableMaterializationCache();
579            }
580            catch(RuntimeException e)
581            {
582                pb.getInternalCache().doLocalClear();
583                throw e;
584            }
585            finally
586            {
587                classToPrefetch = saveClassToPrefetch;
588            }
589        }
590    
591       /**
592         * retrieves an Object reference's Identity.
593         * <br>
594         * Null is returned if all foreign keys are null
595         */
596        private Identity getReferencedObjectIdentity(Object obj, ObjectReferenceDescriptor rds, ClassDescriptor cld)
597        {
598            Object[] fkValues = rds.getForeignKeyValues(obj, cld);
599            FieldDescriptor[] fkFieldDescriptors = rds.getForeignKeyFieldDescriptors(cld);
600            boolean hasNullifiedFKValue = hasNullifiedFK(fkFieldDescriptors, fkValues);
601            /*
602            BRJ: if all fk values are null there's no referenced object
603    
604            arminw: Supposed the given object has nullified FK values but the referenced
605            object still exists. This could happend after serialization of the main object. In
606            this case all anonymous field (AK) information is lost, because AnonymousPersistentField class
607            use the object identity to cache the AK values. But we can build Identity anyway from the reference
608            */
609            if (hasNullifiedFKValue)
610            {
611                if(BrokerHelper.hasAnonymousKeyReference(cld, rds))
612                {
613                    Object referencedObject = rds.getPersistentField().get(obj);
614                    if(referencedObject != null)
615                    {
616                        return pb.serviceIdentity().buildIdentity(referencedObject);
617                    }
618                }
619            }
620            else
621            {
622                // ensure that top-level extents are used for Identities
623                return pb.serviceIdentity().buildIdentity(rds.getItemClass(), pb.getTopLevelClass(rds.getItemClass()), fkValues);
624            }
625            return null;
626        }
627    
628        // BRJ: check if we have non null fk values
629        // TBD  we should also check primitives
630        // to avoid creation of unmaterializable proxies
631        private boolean hasNullifiedFK(FieldDescriptor[] fkFieldDescriptors, Object[] fkValues)
632        {
633            boolean result = true;
634            for (int i = 0; i < fkValues.length; i++)
635            {
636                if (!pb.serviceBrokerHelper().representsNull(fkFieldDescriptors[i], fkValues[i]))
637                {
638                    result = false;
639                    break;
640                }
641            }
642            return result;
643        }
644    
645        /**
646         * retrieves an Object reference by its Identity.
647         * <br>
648         * If there is a Proxy-class is defined in the ReferenceDescriptor or
649         * if the ReferenceDescriptor is lazy, a Proxy-object is returned.
650         * <br>
651         * If no Proxy-class is defined, a getObjectByIdentity(...) lookup is performed.
652         */
653        private Object getReferencedObject(Identity id, ObjectReferenceDescriptor rds)
654        {
655            Class baseClassForProxy;
656    
657            if (rds.isLazy())
658            {
659                /*
660                arminw:
661                use real reference class instead of the top-level class,
662                because we want to use a proxy representing the real class
663                not only the top-level class - right?
664                */
665                // referencedProxy = getClassDescriptor(referencedClass).getDynamicProxyClass();
666                //referencedProxy = rds.getItemClass(); 
667                
668                /*
669                 * andrew.clute:
670                 * With proxy generation now handled by the ProxyFactory implementations, the class of the Item
671                 * is now the nessecary parameter to generate a proxy.
672                 */
673                baseClassForProxy = rds.getItemClass();
674            }
675            else
676            {
677                /*
678                * andrew.clute:
679                * If the descriptor does not mark it as lazy, then the class for the proxy must be of type VirtualProxy
680                */
681               baseClassForProxy = rds.getItemProxyClass();
682            }
683    
684            if (baseClassForProxy != null)
685            {
686                try
687                {
688                    return pb.createProxy(baseClassForProxy, id);
689                }
690                catch (Exception e)
691                {
692                    log.error("Error while instantiate object " + id + ", msg: "+ e.getMessage(), e);
693                    if(e instanceof PersistenceBrokerException)
694                    {
695                        throw (PersistenceBrokerException) e;
696                    }
697                    else
698                    {
699                        throw new PersistenceBrokerException(e);
700                    }
701                }
702            }
703            else
704            {
705                return pb.doGetObjectByIdentity(id);
706            }
707        }
708        
709        /**
710         * Retrieve a single Collection on behalf of <b>obj</b>.
711         * The Collection is retrieved only if <b>cascade.retrieve is true</b>
712         * or if <b>forced</b> is set to true.     *
713         *
714         * @param obj - the object to be updated
715         * @param cld - the ClassDescriptor describing obj
716         * @param cds - the CollectionDescriptor describing the collection attribute to be loaded
717         * @param forced - if set to true loading is forced, even if cds differs.
718         *
719         */
720        public void retrieveCollection(Object obj, ClassDescriptor cld, CollectionDescriptor cds, boolean forced)
721        {
722            doRetrieveCollection(obj, cld, cds, forced, cds.isLazy());
723        }
724        
725        /**
726         * Retrieve a single Proxied Collection on behalf of <b>obj</b>.
727         * The Collection is retrieved only if <b>cascade.retrieve is true</b>
728         * or if <b>forced</b> is set to true.     *
729         *
730         * @param obj - the object to be updated
731         * @param cld - the ClassDescriptor describing obj
732         * @param cds - the CollectionDescriptor describing the collection attribute to be loaded
733         * @param forced - if set to true a proxy will be placed, even if cds differs.
734         *
735         */
736        public void retrieveProxyCollection(Object obj, ClassDescriptor cld, CollectionDescriptor cds, boolean forced)
737        {
738            doRetrieveCollection(obj, cld, cds, forced, true);
739        }
740        
741        private void doRetrieveCollection(Object obj, ClassDescriptor cld, CollectionDescriptor cds, boolean forced, boolean lazyLoad)
742        {
743            if (forced || cds.getCascadeRetrieve())
744            {
745                if ((m_retrievalTasks != null) && !cds.isLazy()
746                        && !cds.hasProxyItems()
747                        && (cds.getQueryCustomizer() == null))
748                {
749                    addRetrievalTask(obj, cds);
750                }
751                else
752                {
753                    // this collection type will be used:
754                    Class collectionClass = cds.getCollectionClass();
755                    PersistentField collectionField = cds.getPersistentField();
756                    Query fkQuery = getFKQuery(obj, cld, cds);
757                    Object value;
758    
759                    pb.getInternalCache().enableMaterializationCache();
760                    try
761                    {
762                        if (collectionClass == null)
763                        {
764                            Collection result = (Collection)getCollectionByQuery(getCollectionTypeClass(cds), fkQuery, lazyLoad);
765    
766                            // assign collection to objects attribute
767                            // if attribute has an array type build an array, else assign collection directly
768                            if (collectionField.getType().isArray())
769                            {
770                                int length = result.size();
771                                Class itemtype = collectionField.getType().getComponentType();
772                                Object resultArray = Array.newInstance(itemtype, length);
773                                int j = 0;
774                                for (Iterator iter = result.iterator(); iter.hasNext();j++)
775                                {
776                                    Array.set(resultArray, j, iter.next());
777                                }
778                                collectionField.set(obj, resultArray);
779                            }
780                            else
781                            {
782                                collectionField.set(obj, result);
783                            }
784                            value = result;
785                        }
786                        else
787                        {
788                            ManageableCollection result = getCollectionByQuery(collectionClass, fkQuery, lazyLoad);
789                            collectionField.set(obj, result);
790                            value = result;
791                        }
792    
793                        if (prefetchProxies && (m_retrievalTasks != null)
794                                && (cds.getProxyPrefetchingLimit() > 0)
795                                && (cds.getQueryCustomizer() == null)
796                                && (ProxyHelper.isCollectionProxy(value)))
797                        {
798                            if (addRetrievalTask(obj, cds))
799                            {
800                                new PBCollectionProxyListener(obj,
801                                        m_retrievalTasks, cds, cds.getProxyPrefetchingLimit());
802                            }
803                        }
804    
805                        pb.getInternalCache().disableMaterializationCache();
806                    }
807                    catch(RuntimeException e)
808                    {
809                        pb.getInternalCache().doLocalClear();
810                        throw e;
811                    }
812                }
813            }
814        }
815    
816        /**
817         * Answer the foreign key query to retrieve the collection
818         * defined by CollectionDescriptor
819         */
820        private Query getFKQuery(Object obj, ClassDescriptor cld, CollectionDescriptor cds)
821        {
822            Query fkQuery;
823            QueryByCriteria fkQueryCrit;
824    
825            if (cds.isMtoNRelation())
826            {
827                fkQueryCrit = getFKQueryMtoN(obj, cld, cds);
828            }
829            else
830            {
831                fkQueryCrit = getFKQuery1toN(obj, cld, cds);
832            }
833    
834            // check if collection must be ordered
835            if (!cds.getOrderBy().isEmpty())
836            {
837                Iterator iter = cds.getOrderBy().iterator();
838                while (iter.hasNext())
839                {
840                    fkQueryCrit.addOrderBy((FieldHelper)iter.next());
841                }
842            }
843    
844            // BRJ: customize the query
845            if (cds.getQueryCustomizer() != null)
846            {
847                fkQuery = cds.getQueryCustomizer().customizeQuery(obj, pb, cds, fkQueryCrit);
848            }
849            else
850            {
851                fkQuery = fkQueryCrit;
852            }
853    
854            return fkQuery;
855        }
856    
857        /**
858         * Get Foreign key query for m:n <br>
859         * supports UNIDIRECTIONAL m:n using QueryByMtoNCriteria
860         * @return org.apache.ojb.broker.query.QueryByCriteria
861         * @param obj the owner of the relationship
862         * @param cld the ClassDescriptor for the owner
863         * @param cod the CollectionDescriptor
864         */
865        private QueryByCriteria getFKQueryMtoN(Object obj, ClassDescriptor cld, CollectionDescriptor cod)
866        {
867            ValueContainer[] values = pb.serviceBrokerHelper().getKeyValues(cld, obj);
868            Object[] thisClassFks = cod.getFksToThisClass();
869            Object[] itemClassFks = cod.getFksToItemClass();
870            ClassDescriptor refCld = pb.getClassDescriptor(cod.getItemClass());
871            Criteria criteria = new Criteria();
872    
873            for (int i = 0; i < thisClassFks.length; i++)
874            {
875                criteria.addEqualTo(cod.getIndirectionTable() + "." + thisClassFks[i], values[i].getValue());
876            }
877            for (int i = 0; i < itemClassFks.length; i++)
878            {
879                criteria.addEqualToField(cod.getIndirectionTable() + "." + itemClassFks[i],
880                        refCld.getPkFields()[i].getAttributeName());
881            }
882    
883            return QueryFactory.newQuery(refCld.getClassOfObject(), cod.getIndirectionTable(), criteria);
884        }
885    
886        /**
887         * Get Foreign key query for 1:n
888         * @return org.apache.ojb.broker.query.QueryByCriteria
889         * @param obj
890         * @param cld
891         * @param cod
892         */
893        private QueryByCriteria getFKQuery1toN(Object obj, ClassDescriptor cld, CollectionDescriptor cod)
894        {
895            ValueContainer[] container = pb.serviceBrokerHelper().getKeyValues(cld, obj);
896            ClassDescriptor refCld = pb.getClassDescriptor(cod.getItemClass());
897            FieldDescriptor[] fields = cod.getForeignKeyFieldDescriptors(refCld);
898            Criteria criteria = new Criteria();
899    
900            for (int i = 0; i < fields.length; i++)
901            {
902                FieldDescriptor fld = fields[i];
903                criteria.addEqualTo(fld.getAttributeName(), container[i].getValue());
904            }
905    
906            return QueryFactory.newQuery(refCld.getClassOfObject(), criteria);
907        }
908    
909        /**
910         * Answer the primary key query to retrieve an Object
911         *
912         * @param oid the Identity of the Object to retrieve
913         * @return The resulting query
914         */
915        public Query getPKQuery(Identity oid)
916        {
917            Object[] values = oid.getPrimaryKeyValues();
918            ClassDescriptor cld = pb.getClassDescriptor(oid.getObjectsTopLevelClass());
919            FieldDescriptor[] fields = cld.getPkFields();
920            Criteria criteria = new Criteria();
921    
922            for (int i = 0; i < fields.length; i++)
923            {
924                FieldDescriptor fld = fields[i];
925                criteria.addEqualTo(fld.getAttributeName(), values[i]);
926            }
927            return QueryFactory.newQuery(cld.getClassOfObject(), criteria);
928        }
929    
930        /**
931         * Retrieve all Collection attributes of a given instance
932         *
933         * @param newObj the instance to be loaded or refreshed
934         * @param cld the ClassDescriptor of the instance
935         * @param forced if set to true, loading is forced even if cld differs
936         *
937         */
938        public void retrieveCollections(Object newObj, ClassDescriptor cld, boolean forced) throws PersistenceBrokerException
939        {
940            doRetrieveCollections(newObj, cld, forced, false);
941        }
942        
943        /**
944         * Retrieve all Collection attributes of a given instance, and make all of the Proxy Collections
945         *
946         * @param newObj the instance to be loaded or refreshed
947         * @param cld the ClassDescriptor of the instance
948         * @param forced if set to true, loading is forced even if cld differs
949         *
950         */
951        public void retrieveProxyCollections(Object newObj, ClassDescriptor cld, boolean forced) throws PersistenceBrokerException
952        {
953            doRetrieveCollections(newObj, cld, forced, true);
954        }
955        
956        private void doRetrieveCollections(Object newObj, ClassDescriptor cld, boolean forced, boolean forceProxyCollection) throws PersistenceBrokerException
957        {
958            Iterator i = cld.getCollectionDescriptors().iterator();
959    
960            // turn off auto prefetching for related proxies
961            final Class saveClassToPrefetch = classToPrefetch;
962            classToPrefetch = null;
963    
964            pb.getInternalCache().enableMaterializationCache();
965            try
966            {
967                while (i.hasNext())
968                {
969                    CollectionDescriptor cds = (CollectionDescriptor) i.next();
970                    if (forceProxyCollection){
971                        retrieveProxyCollection(newObj, cld, cds, forced);
972                    } else {
973                        retrieveCollection(newObj, cld, cds, forced);
974                    }
975                }
976                pb.getInternalCache().disableMaterializationCache();
977            }
978            catch (RuntimeException e)
979            {
980                pb.getInternalCache().doLocalClear();
981                throw e;
982            }
983            finally
984            {
985                classToPrefetch = saveClassToPrefetch;
986            }
987        }
988    
989    
990        /**
991         * remove all prefetching listeners
992         */
993        public void removePrefetchingListeners()
994        {
995            if (prefetchingListeners != null)
996            {
997                for (Iterator it = prefetchingListeners.iterator(); it.hasNext(); )
998                {
999                    PBPrefetchingListener listener = (PBPrefetchingListener) it.next();
1000                    listener.removeThisListener();
1001                }
1002                prefetchingListeners.clear();
1003            }
1004        }
1005    
1006        public Class getClassToPrefetch()
1007        {
1008            return classToPrefetch;
1009        }
1010    
1011        //**********************************************************************
1012        // inner classes
1013        //**********************************************************************
1014    
1015        class PBMaterializationListener extends PBPrefetchingListener implements MaterializationListener
1016        {
1017            private IndirectionHandler _listenedHandler;        
1018    
1019            PBMaterializationListener(Object owner,
1020                    HashMap retrievalTasks, Object key, int limit)
1021            {
1022                super(owner, retrievalTasks, key, limit);            
1023            }
1024    
1025            protected void addThisListenerTo(Object owner)
1026            {
1027                _listenedHandler = ProxyHelper.getIndirectionHandler(owner);
1028    
1029                if (_listenedHandler != null)
1030                {
1031                    _listenedHandler.addListener(this);
1032                }
1033            }
1034    
1035            protected void removeThisListener()
1036            {
1037                if (_listenedHandler != null)
1038                {
1039                    _listenedHandler.removeListener(this);
1040                    _listenedHandler = null;
1041                }
1042            }
1043    
1044            protected RelationshipPrefetcher getPrefetcher(Object listenedObject)
1045            {
1046                if (_key instanceof ObjectReferenceDescriptor)
1047                {
1048                    return pb.getRelationshipPrefetcherFactory().createRelationshipPrefetcher((ObjectReferenceDescriptor) _key);
1049                }
1050                else // PersistentBrokerImpl.this
1051                {
1052                    // a special case: current collection being loaded contains proxies,
1053                    // just load them without setting any fields
1054                    IndirectionHandler handler = (IndirectionHandler) listenedObject;
1055                    return new PlainPrefetcher(pb, handler.getIdentity().getObjectsTopLevelClass());
1056                }
1057            }
1058    
1059            public void beforeMaterialization(IndirectionHandler handler, Identity oid)
1060            {
1061                prefetch(handler);
1062            }
1063    
1064            public void afterMaterialization(IndirectionHandler handler, Object materializedObject)
1065            {
1066                //do nothing            
1067            }
1068        }
1069    
1070        abstract class PBPrefetchingListener
1071        {
1072            private HashMap _retrievalTasks;
1073            private int _limit;
1074            protected Object _key;
1075    
1076            PBPrefetchingListener(Object owner, HashMap retrievalTasks,
1077                    Object key, int limit)
1078            {
1079                _retrievalTasks = retrievalTasks;
1080                _key = key;
1081                _limit = limit + 1; // lestenedObject + next limit objects
1082                if (prefetchingListeners == null)
1083                {
1084                    prefetchingListeners = new ArrayList();
1085                }
1086                addThisListenerTo(owner);
1087                prefetchingListeners.add(this);
1088            }
1089    
1090            abstract protected void addThisListenerTo(Object owner);
1091    
1092            abstract protected void removeThisListener();
1093    
1094            abstract protected RelationshipPrefetcher getPrefetcher(Object listenedObject);
1095    
1096            protected void prefetch(Object listenedObject)
1097            {
1098                ArrayList owners = (ArrayList) _retrievalTasks.get(_key);
1099                List toPrefetch;
1100                RelationshipPrefetcher prefetcher;
1101                boolean prefetchingAll;
1102    
1103                removeThisListener();
1104    
1105                if (owners == null)
1106                {
1107                    return;
1108                }
1109    
1110                prefetcher = getPrefetcher(listenedObject);
1111    
1112                if (owners.size() <= _limit)
1113                {
1114                    toPrefetch = owners;
1115                    prefetchingAll = true;
1116                }
1117                else
1118                {
1119                    toPrefetch = owners.subList(0, _limit);
1120                    prefetchingAll = false;
1121                }
1122    
1123                final Class saveClassToPrefetch = classToPrefetch;
1124                classToPrefetch = prefetcher.getItemClassDescriptor().getClassOfObject();
1125                try
1126                {
1127                    prefetcher.prefetchRelationship(toPrefetch);
1128                }
1129                finally
1130                {
1131                    classToPrefetch = saveClassToPrefetch;
1132                }
1133    
1134                if (prefetchingAll)
1135                {
1136                    _retrievalTasks.remove(_key);
1137                }
1138                else
1139                {
1140                    // ArrayList documented trick:
1141                    // "the following idiom removes a range of elements from a list:
1142                    // list.subList(from, to).clear();
1143                    toPrefetch.clear();
1144                    addThisListenerTo(owners.get(0));
1145                }
1146            }
1147        }
1148    
1149        class PBCollectionProxyListener extends PBPrefetchingListener
1150                implements CollectionProxyListener
1151        {
1152            CollectionProxyDefaultImpl _listenedCollection;
1153    
1154            PBCollectionProxyListener(Object owner,
1155                   HashMap retrievalTasks, CollectionDescriptor key, int limit)
1156            {
1157                super(owner, retrievalTasks, key, limit);
1158            }
1159    
1160            protected void addThisListenerTo(Object owner)
1161            {
1162                PersistentField collectionField =
1163                        ((CollectionDescriptor) _key).getPersistentField();
1164                _listenedCollection = (CollectionProxyDefaultImpl) collectionField.get(owner);
1165                _listenedCollection.addListener(this);
1166            }
1167    
1168            protected void removeThisListener()
1169            {
1170                if (_listenedCollection != null)
1171                {
1172                    _listenedCollection.removeListener(this);
1173                    _listenedCollection = null;
1174                }
1175            }
1176    
1177            protected RelationshipPrefetcher getPrefetcher(Object listenedObject)
1178            {
1179                return pb.getRelationshipPrefetcherFactory().createRelationshipPrefetcher((CollectionDescriptor)_key);
1180            }
1181    
1182            public void beforeLoading(CollectionProxyDefaultImpl col)
1183            {
1184                prefetch(col);
1185            }
1186    
1187            public void afterLoading(CollectionProxyDefaultImpl col)
1188            {
1189                //do nothing
1190            }
1191        }
1192    }