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 }