1 //$Id: CollectionLoadContext.java 7764 2005-08-05 16:16:46Z oneovthafew $
2 package org.hibernate.engine;
3
4 import java.io.Serializable;
5 import java.util.ArrayList;
6 import java.util.Comparator;
7 import java.util.HashMap;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.Map;
11
12 import org.apache.commons.logging.Log;
13 import org.apache.commons.logging.LogFactory;
14 import org.hibernate.CacheMode;
15 import org.hibernate.EntityMode;
16 import org.hibernate.HibernateException;
17 import org.hibernate.cache.CacheKey;
18 import org.hibernate.cache.entry.CollectionCacheEntry;
19 import org.hibernate.collection.PersistentCollection;
20 import org.hibernate.persister.collection.CollectionPersister;
21 import org.hibernate.pretty.MessageHelper;
22
23 /**
24 * Represents the state of collections currently being loaded. Eventually, I
25 * would like to have multiple instances of this per session - one per JDBC
26 * result set, instead of the resultSetId being passed.
27 * @author Gavin King
28 */
29 public class CollectionLoadContext {
30
31 private static final Log log = LogFactory.getLog(CollectionLoadContext.class);
32
33 // The collections we are currently loading
34 private final Map loadingCollections = new HashMap(8);
35 private final PersistenceContext context;
36
37 public CollectionLoadContext(PersistenceContext context) {
38 this.context = context;
39 }
40
41 private static final class LoadingCollectionEntry {
42
43 final PersistentCollection collection;
44 final Serializable key;
45 final Object resultSetId;
46 final CollectionPersister persister;
47
48 LoadingCollectionEntry(
49 final PersistentCollection collection,
50 final Serializable key,
51 final CollectionPersister persister,
52 final Object resultSetId
53 ) {
54 this.collection = collection;
55 this.key = key;
56 this.persister = persister;
57 this.resultSetId = resultSetId;
58 }
59 }
60
61 /**
62 * Retrieve a collection that is in the process of being loaded, instantiating
63 * a new collection if there is nothing for the given id, or returning null
64 * if the collection with the given id is already fully loaded in the session
65 */
66 public PersistentCollection getLoadingCollection(
67 final CollectionPersister persister,
68 final Serializable key,
69 final Object resultSetId,
70 final EntityMode em)
71 throws HibernateException {
72 CollectionKey ckey = new CollectionKey(persister, key, em);
73 LoadingCollectionEntry lce = getLoadingCollectionEntry(ckey);
74 if ( lce == null ) {
75 //look for existing collection
76 PersistentCollection collection = context.getCollection(ckey);
77 if ( collection != null ) {
78 if ( collection.wasInitialized() ) {
79 log.trace( "collection already initialized: ignoring" );
80 return null; //ignore this row of results! Note the early exit
81 }
82 else {
83 //initialize this collection
84 log.trace( "uninitialized collection: initializing" );
85 }
86 }
87 else {
88 Object entity = context.getCollectionOwner(key, persister);
89 final boolean newlySavedEntity = entity != null &&
90 context.getEntry(entity).getStatus() != Status.LOADING &&
91 em!=EntityMode.DOM4J;
92 if ( newlySavedEntity ) {
93 //important, to account for newly saved entities in query
94 //TODO: some kind of check for new status...
95 log.trace( "owning entity already loaded: ignoring" );
96 return null;
97 }
98 else {
99 //create one
100 log.trace( "new collection: instantiating" );
101 collection = persister.getCollectionType()
102 .instantiate( context.getSession(), persister, key );
103 }
104 }
105 collection.beforeInitialize(persister);
106 collection.beginRead();
107 addLoadingCollectionEntry(ckey, collection, persister, resultSetId);
108 return collection;
109 }
110 else {
111 if ( lce.resultSetId == resultSetId ) {
112 log.trace( "reading row" );
113 return lce.collection;
114 }
115 else {
116 // ignore this row, the collection is in process of
117 // being loaded somewhere further "up" the stack
118 log.trace( "collection is already being initialized: ignoring row" );
119 return null;
120 }
121 }
122 }
123
124 /**
125 * Retrieve a collection that is in the process of being loaded, returning null
126 * if there is no loading collection with the given id
127 */
128 public PersistentCollection getLoadingCollection(CollectionPersister persister, Serializable id, EntityMode em) {
129 LoadingCollectionEntry lce = getLoadingCollectionEntry( new CollectionKey(persister, id, em) );
130 if ( lce != null ) {
131 if ( log.isTraceEnabled() ) {
132 log.trace(
133 "returning loading collection:" +
134 MessageHelper.collectionInfoString(persister, id, context.getSession().getFactory())
135 );
136 }
137 return lce.collection;
138 }
139 else {
140 if ( log.isTraceEnabled() ) {
141 log.trace(
142 "creating collection wrapper:" +
143 MessageHelper.collectionInfoString(persister, id, context.getSession().getFactory())
144 );
145 }
146 return null;
147 }
148 }
149
150 /**
151 * Create a new loading collection entry
152 */
153 private void addLoadingCollectionEntry(
154 final CollectionKey collectionKey,
155 final PersistentCollection collection,
156 final CollectionPersister persister,
157 final Object resultSetId
158 ) {
159 loadingCollections.put(
160 collectionKey,
161 new LoadingCollectionEntry(
162 collection,
163 collectionKey.getKey(),
164 persister,
165 resultSetId
166 )
167 );
168 }
169
170 /**
171 * get an existing new loading collection entry
172 */
173 private LoadingCollectionEntry getLoadingCollectionEntry(CollectionKey collectionKey) {
174 return ( LoadingCollectionEntry ) loadingCollections.get( collectionKey );
175 }
176
177 /**
178 * After we have finished processing a result set, a particular loading collection that
179 * we are done.
180 */
181 private void endLoadingCollection(LoadingCollectionEntry lce, CollectionPersister persister, EntityMode em) {
182
183 boolean hasNoQueuedAdds = lce.collection.endRead(); //warning: can cause a recursive query! (proxy initialization)
184
185 if ( persister.getCollectionType().hasHolder(em) ) {
186 context.addCollectionHolder(lce.collection);
187 }
188
189 CollectionEntry ce = context.getCollectionEntry(lce.collection);
190 if ( ce==null ) {
191 ce = context.addInitializedCollection(persister, lce.collection, lce.key);
192 }
193 else {
194 ce.postInitialize(lce.collection);
195 }
196
197 final SessionImplementor session = context.getSession();
198
199 boolean addToCache = hasNoQueuedAdds && // there were no queued additions
200 persister.hasCache() && // and the role has a cache
201 session.getCacheMode().isPutEnabled() &&
202 !ce.isDoremove(); // and this is not a forced initialization during flush
203 if (addToCache) addCollectionToCache(lce, persister);
204
205 if ( log.isDebugEnabled() ) {
206 log.debug(
207 "collection fully initialized: " +
208 MessageHelper.collectionInfoString(persister, lce.key, context.getSession().getFactory())
209 );
210 }
211
212 if ( session.getFactory().getStatistics().isStatisticsEnabled() ) {
213 session.getFactory().getStatisticsImplementor().loadCollection(
214 persister.getRole()
215 );
216 }
217
218 }
219 /**
220 * Finish the process of loading collections for a particular result set
221 */
222 public void endLoadingCollections(CollectionPersister persister, Object resultSetId, SessionImplementor session)
223 throws HibernateException {
224
225 // scan the loading collections for collections from this result set
226 // put them in a new temp collection so that we are safe from concurrent
227 // modification when the call to endRead() causes a proxy to be
228 // initialized
229 List resultSetCollections = null; //TODO: make this the resultSetId?
230 Iterator iter = loadingCollections.values().iterator();
231 while ( iter.hasNext() ) {
232 LoadingCollectionEntry lce = (LoadingCollectionEntry) iter.next();
233 if ( lce.resultSetId == resultSetId && lce.persister==persister) {
234 if ( resultSetCollections == null ) {
235 resultSetCollections = new ArrayList();
236 }
237 resultSetCollections.add(lce);
238 if ( lce.collection.getOwner()==null ) {
239 session.getPersistenceContext()
240 .addUnownedCollection(
241 new CollectionKey( persister, lce.key, session.getEntityMode() ),
242 lce.collection
243 );
244 }
245 iter.remove();
246 }
247 }
248
249 endLoadingCollections( persister, resultSetCollections, session.getEntityMode() );
250 }
251
252 /**
253 * After we have finished processing a result set, notify the loading collections that
254 * we are done.
255 */
256 private void endLoadingCollections(CollectionPersister persister, List resultSetCollections, EntityMode em)
257 throws HibernateException {
258
259 final int count = (resultSetCollections == null) ? 0 : resultSetCollections.size();
260
261 if ( log.isDebugEnabled() ) {
262 log.debug( count + " collections were found in result set for role: " + persister.getRole() );
263 }
264
265 //now finish them
266 for ( int i = 0; i < count; i++ ) {
267 LoadingCollectionEntry lce = (LoadingCollectionEntry) resultSetCollections.get(i);
268 endLoadingCollection(lce, persister, em);
269 }
270
271 if ( log.isDebugEnabled() ) {
272 log.debug( count + " collections initialized for role: " + persister.getRole() );
273 }
274 }
275
276 /**
277 * Add a collection to the second-level cache
278 */
279 private void addCollectionToCache(LoadingCollectionEntry lce, CollectionPersister persister) {
280
281 if ( log.isDebugEnabled() ) {
282 log.debug(
283 "Caching collection: " +
284 MessageHelper.collectionInfoString( persister, lce.key, context.getSession().getFactory() )
285 );
286 }
287
288 final SessionImplementor session = context.getSession();
289 final SessionFactoryImplementor factory = session.getFactory();
290
291 if ( !session.getEnabledFilters().isEmpty() && persister.isAffectedByEnabledFilters( session ) ) {
292 // some filters affecting the collection are enabled on the session, so do not do the put into the cache.
293 log.debug( "Refusing to add to cache due to enabled filters" );
294 // todo : add the notion of enabled filters to the CacheKey to differentiate filtered collections from non-filtered;
295 // but CacheKey is currently used for both collections and entities; would ideally need to define two seperate ones;
296 // currently this works in conjuction with the check on
297 // DefaultInitializeCollectionEventHandler.initializeCollectionFromCache() (which makes sure to not read from
298 // cache with enabled filters).
299 return; // EARLY EXIT!!!!!
300 }
301
302 final Comparator versionComparator;
303 final Object version;
304 if ( persister.isVersioned() ) {
305 versionComparator = persister.getOwnerEntityPersister().getVersionType().getComparator();
306 version = context.getEntry( context.getCollectionOwner(lce.key, persister) ).getVersion();
307 }
308 else {
309 version = null;
310 versionComparator = null;
311 }
312
313 CollectionCacheEntry entry = new CollectionCacheEntry(lce.collection, persister);
314
315 CacheKey cacheKey = new CacheKey(
316 lce.key,
317 persister.getKeyType(),
318 persister.getRole(),
319 session.getEntityMode(),
320 session.getFactory()
321 );
322 boolean put = persister.getCache().put(
323 cacheKey,
324 persister.getCacheEntryStructure().structure(entry),
325 session.getTimestamp(),
326 version,
327 versionComparator,
328 factory.getSettings().isMinimalPutsEnabled() &&
329 session.getCacheMode()!=CacheMode.REFRESH
330 );
331
332 if ( put && factory.getStatistics().isStatisticsEnabled() ) {
333 factory.getStatisticsImplementor().secondLevelCachePut(
334 persister.getCache().getRegionName()
335 );
336 }
337 }
338
339
340 }