1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.event.def;
26
27 import java.io.Serializable;
28
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 import org.hibernate.HibernateException;
32 import org.hibernate.LockMode;
33 import org.hibernate.NonUniqueObjectException;
34 import org.hibernate.PersistentObjectException;
35 import org.hibernate.TypeMismatchException;
36 import org.hibernate.EntityMode;
37 import org.hibernate.cache.CacheKey;
38 import org.hibernate.cache.access.SoftLock;
39 import org.hibernate.cache.entry.CacheEntry;
40 import org.hibernate.engine.EntityEntry;
41 import org.hibernate.engine.EntityKey;
42 import org.hibernate.engine.PersistenceContext;
43 import org.hibernate.engine.SessionFactoryImplementor;
44 import org.hibernate.engine.SessionImplementor;
45 import org.hibernate.engine.Status;
46 import org.hibernate.engine.TwoPhaseLoad;
47 import org.hibernate.engine.Versioning;
48 import org.hibernate.event.EventSource;
49 import org.hibernate.event.LoadEvent;
50 import org.hibernate.event.LoadEventListener;
51 import org.hibernate.event.PostLoadEvent;
52 import org.hibernate.event.PostLoadEventListener;
53 import org.hibernate.persister.entity.EntityPersister;
54 import org.hibernate.pretty.MessageHelper;
55 import org.hibernate.proxy.HibernateProxy;
56 import org.hibernate.proxy.LazyInitializer;
57 import org.hibernate.type.Type;
58 import org.hibernate.type.TypeFactory;
59
60 /**
61 * Defines the default load event listeners used by hibernate for loading entities
62 * in response to generated load events.
63 *
64 * @author Steve Ebersole
65 */
66 public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener implements LoadEventListener {
67
68 public static final Object REMOVED_ENTITY_MARKER = new Object();
69 public static final Object INCONSISTENT_RTN_CLASS_MARKER = new Object();
70 public static final LockMode DEFAULT_LOCK_MODE = LockMode.NONE;
71
72 private static final Logger log = LoggerFactory.getLogger(DefaultLoadEventListener.class);
73
74
75 /**
76 * Handle the given load event.
77 *
78 * @param event The load event to be handled.
79 * @throws HibernateException
80 */
81 public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType) throws HibernateException {
82
83 final SessionImplementor source = event.getSession();
84
85 EntityPersister persister;
86 if ( event.getInstanceToLoad() != null ) {
87 persister = source.getEntityPersister( null, event.getInstanceToLoad() ); //the load() which takes an entity does not pass an entityName
88 event.setEntityClassName( event.getInstanceToLoad().getClass().getName() );
89 }
90 else {
91 persister = source.getFactory().getEntityPersister( event.getEntityClassName() );
92 }
93
94 if ( persister == null ) {
95 throw new HibernateException(
96 "Unable to locate persister: " +
97 event.getEntityClassName()
98 );
99 }
100
101 if ( persister.getIdentifierType().isComponentType() && EntityMode.DOM4J == event.getSession().getEntityMode() ) {
102 // skip this check for composite-ids relating to dom4j entity-mode;
103 // alternatively, we could add a check to make sure the incoming id value is
104 // an instance of Element...
105 }
106 else {
107 Class idClass = persister.getIdentifierType().getReturnedClass();
108 if ( idClass != null && ! idClass.isInstance( event.getEntityId() ) ) {
109 throw new TypeMismatchException(
110 "Provided id of the wrong type for class " + persister.getEntityName() + ". Expected: " + idClass + ", got " + event.getEntityId().getClass()
111 );
112 }
113 }
114
115 EntityKey keyToLoad = new EntityKey( event.getEntityId(), persister, source.getEntityMode() );
116
117 try {
118 if ( loadType.isNakedEntityReturned() ) {
119 //do not return a proxy!
120 //(this option indicates we are initializing a proxy)
121 event.setResult( load(event, persister, keyToLoad, loadType) );
122 }
123 else {
124 //return a proxy if appropriate
125 if ( event.getLockMode() == LockMode.NONE ) {
126 event.setResult( proxyOrLoad(event, persister, keyToLoad, loadType) );
127 }
128 else {
129 event.setResult( lockAndLoad(event, persister, keyToLoad, loadType, source) );
130 }
131 }
132 }
133 catch(HibernateException e) {
134 log.info("Error performing load command", e);
135 throw e;
136 }
137 }
138
139 /**
140 * Perfoms the load of an entity.
141 *
142 * @param event The initiating load request event
143 * @param persister The persister corresponding to the entity to be loaded
144 * @param keyToLoad The key of the entity to be loaded
145 * @param options The defined load options
146 * @return The loaded entity.
147 * @throws HibernateException
148 */
149 protected Object load(
150 final LoadEvent event,
151 final EntityPersister persister,
152 final EntityKey keyToLoad,
153 final LoadEventListener.LoadType options) {
154
155 if ( event.getInstanceToLoad() != null ) {
156 if ( event.getSession().getPersistenceContext().getEntry( event.getInstanceToLoad() ) != null ) {
157 throw new PersistentObjectException(
158 "attempted to load into an instance that was already associated with the session: " +
159 MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
160 );
161 }
162 persister.setIdentifier( event.getInstanceToLoad(), event.getEntityId(), event.getSession().getEntityMode() );
163 }
164
165 Object entity = doLoad(event, persister, keyToLoad, options);
166
167 boolean isOptionalInstance = event.getInstanceToLoad() != null;
168
169 if ( !options.isAllowNulls() || isOptionalInstance ) {
170 if ( entity == null ) {
171 event.getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound( event.getEntityClassName(), event.getEntityId() );
172 }
173 }
174
175 if ( isOptionalInstance && entity != event.getInstanceToLoad() ) {
176 throw new NonUniqueObjectException( event.getEntityId(), event.getEntityClassName() );
177 }
178
179 return entity;
180 }
181
182 /**
183 * Based on configured options, will either return a pre-existing proxy,
184 * generate a new proxy, or perform an actual load.
185 *
186 * @param event The initiating load request event
187 * @param persister The persister corresponding to the entity to be loaded
188 * @param keyToLoad The key of the entity to be loaded
189 * @param options The defined load options
190 * @return The result of the proxy/load operation.
191 */
192 protected Object proxyOrLoad(
193 final LoadEvent event,
194 final EntityPersister persister,
195 final EntityKey keyToLoad,
196 final LoadEventListener.LoadType options) {
197
198 if ( log.isTraceEnabled() ) {
199 log.trace(
200 "loading entity: " +
201 MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
202 );
203 }
204
205 if ( !persister.hasProxy() ) {
206 // this class has no proxies (so do a shortcut)
207 return load(event, persister, keyToLoad, options);
208 }
209 else {
210 final PersistenceContext persistenceContext = event.getSession().getPersistenceContext();
211
212 // look for a proxy
213 Object proxy = persistenceContext.getProxy(keyToLoad);
214 if ( proxy != null ) {
215 return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy );
216 }
217 else {
218 if ( options.isAllowProxyCreation() ) {
219 return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext );
220 }
221 else {
222 // return a newly loaded object
223 return load(event, persister, keyToLoad, options);
224 }
225 }
226
227 }
228 }
229
230 /**
231 * Given a proxy, initialize it and/or narrow it provided either
232 * is necessary.
233 *
234 * @param event The initiating load request event
235 * @param persister The persister corresponding to the entity to be loaded
236 * @param keyToLoad The key of the entity to be loaded
237 * @param options The defined load options
238 * @param persistenceContext The originating session
239 * @param proxy The proxy to narrow
240 * @return The created/existing proxy
241 */
242 private Object returnNarrowedProxy(
243 final LoadEvent event,
244 final EntityPersister persister,
245 final EntityKey keyToLoad,
246 final LoadEventListener.LoadType options,
247 final PersistenceContext persistenceContext,
248 final Object proxy) {
249 log.trace("entity proxy found in session cache");
250 LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer();
251 if ( li.isUnwrap() ) {
252 return li.getImplementation();
253 }
254 Object impl = null;
255 if ( !options.isAllowProxyCreation() ) {
256 impl = load( event, persister, keyToLoad, options );
257 if ( impl == null ) {
258 event.getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound( persister.getEntityName(), keyToLoad.getIdentifier());
259 }
260 }
261 return persistenceContext.narrowProxy( proxy, persister, keyToLoad, impl );
262 }
263
264 /**
265 * If there is already a corresponding proxy associated with the
266 * persistence context, return it; otherwise create a proxy, associate it
267 * with the persistence context, and return the just-created proxy.
268 *
269 * @param event The initiating load request event
270 * @param persister The persister corresponding to the entity to be loaded
271 * @param keyToLoad The key of the entity to be loaded
272 * @param options The defined load options
273 * @param persistenceContext The originating session
274 * @return The created/existing proxy
275 */
276 private Object createProxyIfNecessary(
277 final LoadEvent event,
278 final EntityPersister persister,
279 final EntityKey keyToLoad,
280 final LoadEventListener.LoadType options,
281 final PersistenceContext persistenceContext) {
282 Object existing = persistenceContext.getEntity( keyToLoad );
283 if ( existing != null ) {
284 // return existing object or initialized proxy (unless deleted)
285 log.trace( "entity found in session cache" );
286 if ( options.isCheckDeleted() ) {
287 EntityEntry entry = persistenceContext.getEntry( existing );
288 Status status = entry.getStatus();
289 if ( status == Status.DELETED || status == Status.GONE ) {
290 return null;
291 }
292 }
293 return existing;
294 }
295 else {
296 log.trace( "creating new proxy for entity" );
297 // return new uninitialized proxy
298 Object proxy = persister.createProxy( event.getEntityId(), event.getSession() );
299 persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey(keyToLoad);
300 persistenceContext.addProxy(keyToLoad, proxy);
301 return proxy;
302 }
303 }
304
305 /**
306 * If the class to be loaded has been configured with a cache, then lock
307 * given id in that cache and then perform the load.
308 *
309 * @param event The initiating load request event
310 * @param persister The persister corresponding to the entity to be loaded
311 * @param keyToLoad The key of the entity to be loaded
312 * @param options The defined load options
313 * @param source The originating session
314 * @return The loaded entity
315 * @throws HibernateException
316 */
317 protected Object lockAndLoad(
318 final LoadEvent event,
319 final EntityPersister persister,
320 final EntityKey keyToLoad,
321 final LoadEventListener.LoadType options,
322 final SessionImplementor source) {
323 SoftLock lock = null;
324 final CacheKey ck;
325 if ( persister.hasCache() ) {
326 ck = new CacheKey(
327 event.getEntityId(),
328 persister.getIdentifierType(),
329 persister.getRootEntityName(),
330 source.getEntityMode(),
331 source.getFactory()
332 );
333 lock = persister.getCacheAccessStrategy().lockItem( ck, null );
334 }
335 else {
336 ck = null;
337 }
338
339 Object entity;
340 try {
341 entity = load(event, persister, keyToLoad, options);
342 }
343 finally {
344 if ( persister.hasCache() ) {
345 persister.getCacheAccessStrategy().unlockItem( ck, lock );
346 }
347 }
348
349 return event.getSession().getPersistenceContext().proxyFor( persister, keyToLoad, entity );
350 }
351
352
353 /**
354 * Coordinates the efforts to load a given entity. First, an attempt is
355 * made to load the entity from the session-level cache. If not found there,
356 * an attempt is made to locate it in second-level cache. Lastly, an
357 * attempt is made to load it directly from the datasource.
358 *
359 * @param event The load event
360 * @param persister The persister for the entity being requested for load
361 * @param keyToLoad The EntityKey representing the entity to be loaded.
362 * @param options The load options.
363 * @return The loaded entity, or null.
364 */
365 protected Object doLoad(
366 final LoadEvent event,
367 final EntityPersister persister,
368 final EntityKey keyToLoad,
369 final LoadEventListener.LoadType options) {
370
371 if ( log.isTraceEnabled() ) {
372 log.trace(
373 "attempting to resolve: " +
374 MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
375 );
376 }
377
378 Object entity = loadFromSessionCache( event, keyToLoad, options );
379 if ( entity == REMOVED_ENTITY_MARKER ) {
380 log.debug( "load request found matching entity in context, but it is scheduled for removal; returning null" );
381 return null;
382 }
383 if ( entity == INCONSISTENT_RTN_CLASS_MARKER ) {
384 log.debug( "load request found matching entity in context, but the matched entity was of an inconsistent return type; returning null" );
385 return null;
386 }
387 if ( entity != null ) {
388 if ( log.isTraceEnabled() ) {
389 log.trace(
390 "resolved object in session cache: " +
391 MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
392 );
393 }
394 return entity;
395 }
396
397 entity = loadFromSecondLevelCache(event, persister, options);
398 if ( entity != null ) {
399 if ( log.isTraceEnabled() ) {
400 log.trace(
401 "resolved object in second-level cache: " +
402 MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
403 );
404 }
405 return entity;
406 }
407
408 if ( log.isTraceEnabled() ) {
409 log.trace(
410 "object not resolved in any cache: " +
411 MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
412 );
413 }
414
415 return loadFromDatasource(event, persister, keyToLoad, options);
416 }
417
418 /**
419 * Performs the process of loading an entity from the configured
420 * underlying datasource.
421 *
422 * @param event The load event
423 * @param persister The persister for the entity being requested for load
424 * @param keyToLoad The EntityKey representing the entity to be loaded.
425 * @param options The load options.
426 * @return The object loaded from the datasource, or null if not found.
427 */
428 protected Object loadFromDatasource(
429 final LoadEvent event,
430 final EntityPersister persister,
431 final EntityKey keyToLoad,
432 final LoadEventListener.LoadType options) {
433 final SessionImplementor source = event.getSession();
434 Object entity = persister.load(
435 event.getEntityId(),
436 event.getInstanceToLoad(),
437 event.getLockMode(),
438 source
439 );
440
441 if ( event.isAssociationFetch() && source.getFactory().getStatistics().isStatisticsEnabled() ) {
442 source.getFactory().getStatisticsImplementor().fetchEntity( event.getEntityClassName() );
443 }
444
445 return entity;
446 }
447
448 /**
449 * Attempts to locate the entity in the session-level cache.
450 * <p/>
451 * If allowed to return nulls, then if the entity happens to be found in
452 * the session cache, we check the entity type for proper handling
453 * of entity hierarchies.
454 * <p/>
455 * If checkDeleted was set to true, then if the entity is found in the
456 * session-level cache, it's current status within the session cache
457 * is checked to see if it has previously been scheduled for deletion.
458 *
459 * @param event The load event
460 * @param keyToLoad The EntityKey representing the entity to be loaded.
461 * @param options The load options.
462 * @return The entity from the session-level cache, or null.
463 * @throws HibernateException Generally indicates problems applying a lock-mode.
464 */
465 protected Object loadFromSessionCache(
466 final LoadEvent event,
467 final EntityKey keyToLoad,
468 final LoadEventListener.LoadType options) throws HibernateException {
469
470 SessionImplementor session = event.getSession();
471 Object old = session.getEntityUsingInterceptor( keyToLoad );
472
473 if ( old != null ) {
474 // this object was already loaded
475 EntityEntry oldEntry = session.getPersistenceContext().getEntry( old );
476 if ( options.isCheckDeleted() ) {
477 Status status = oldEntry.getStatus();
478 if ( status == Status.DELETED || status == Status.GONE ) {
479 return REMOVED_ENTITY_MARKER;
480 }
481 }
482 if ( options.isAllowNulls() ) {
483 EntityPersister persister = event.getSession().getFactory().getEntityPersister( event.getEntityClassName() );
484 if ( ! persister.isInstance( old, event.getSession().getEntityMode() ) ) {
485 return INCONSISTENT_RTN_CLASS_MARKER;
486 }
487 }
488 upgradeLock( old, oldEntry, event.getLockMode(), session );
489 }
490
491 return old;
492 }
493
494 /**
495 * Attempts to load the entity from the second-level cache.
496 *
497 * @param event The load event
498 * @param persister The persister for the entity being requested for load
499 * @param options The load options.
500 * @return The entity from the second-level cache, or null.
501 */
502 protected Object loadFromSecondLevelCache(
503 final LoadEvent event,
504 final EntityPersister persister,
505 final LoadEventListener.LoadType options) {
506
507 final SessionImplementor source = event.getSession();
508
509 final boolean useCache = persister.hasCache()
510 && source.getCacheMode().isGetEnabled()
511 && event.getLockMode().lessThan(LockMode.READ);
512
513 if ( useCache ) {
514
515 final SessionFactoryImplementor factory = source.getFactory();
516
517 final CacheKey ck = new CacheKey(
518 event.getEntityId(),
519 persister.getIdentifierType(),
520 persister.getRootEntityName(),
521 source.getEntityMode(),
522 source.getFactory()
523 );
524 Object ce = persister.getCacheAccessStrategy().get( ck, source.getTimestamp() );
525 if ( factory.getStatistics().isStatisticsEnabled() ) {
526 if ( ce == null ) {
527 factory.getStatisticsImplementor().secondLevelCacheMiss(
528 persister.getCacheAccessStrategy().getRegion().getName()
529 );
530 }
531 else {
532 factory.getStatisticsImplementor().secondLevelCacheHit(
533 persister.getCacheAccessStrategy().getRegion().getName()
534 );
535 }
536 }
537
538 if ( ce != null ) {
539 CacheEntry entry = (CacheEntry) persister.getCacheEntryStructure().destructure( ce, factory );
540
541 // Entity was found in second-level cache...
542 return assembleCacheEntry(
543 entry,
544 event.getEntityId(),
545 persister,
546 event
547 );
548 }
549 }
550
551 return null;
552 }
553
554 private Object assembleCacheEntry(
555 final CacheEntry entry,
556 final Serializable id,
557 final EntityPersister persister,
558 final LoadEvent event) throws HibernateException {
559
560 final Object optionalObject = event.getInstanceToLoad();
561 final EventSource session = event.getSession();
562 final SessionFactoryImplementor factory = session.getFactory();
563
564 if ( log.isTraceEnabled() ) {
565 log.trace(
566 "assembling entity from second-level cache: " +
567 MessageHelper.infoString( persister, id, factory )
568 );
569 }
570
571 EntityPersister subclassPersister = factory.getEntityPersister( entry.getSubclass() );
572 Object result = optionalObject == null ?
573 session.instantiate( subclassPersister, id ) : optionalObject;
574
575 // make it circular-reference safe
576 TwoPhaseLoad.addUninitializedCachedEntity(
577 new EntityKey( id, subclassPersister, session.getEntityMode() ),
578 result,
579 subclassPersister,
580 LockMode.NONE,
581 entry.areLazyPropertiesUnfetched(),
582 entry.getVersion(),
583 session
584 );
585
586 Type[] types = subclassPersister.getPropertyTypes();
587 Object[] values = entry.assemble( result, id, subclassPersister, session.getInterceptor(), session ); // intializes result by side-effect
588 TypeFactory.deepCopy(
589 values,
590 types,
591 subclassPersister.getPropertyUpdateability(),
592 values,
593 session
594 );
595
596 Object version = Versioning.getVersion( values, subclassPersister );
597 if ( log.isTraceEnabled() ) log.trace( "Cached Version: " + version );
598
599 final PersistenceContext persistenceContext = session.getPersistenceContext();
600 persistenceContext.addEntry(
601 result,
602 Status.MANAGED,
603 values,
604 null,
605 id,
606 version,
607 LockMode.NONE,
608 true,
609 subclassPersister,
610 false,
611 entry.areLazyPropertiesUnfetched()
612 );
613 subclassPersister.afterInitialize( result, entry.areLazyPropertiesUnfetched(), session );
614 persistenceContext.initializeNonLazyCollections();
615 // upgrade the lock if necessary:
616 //lock(result, lockMode);
617
618 //PostLoad is needed for EJB3
619 //TODO: reuse the PostLoadEvent...
620 PostLoadEvent postLoadEvent = new PostLoadEvent(session).setEntity(result)
621 .setId(id).setPersister(persister);
622 PostLoadEventListener[] listeners = session.getListeners().getPostLoadEventListeners();
623 for ( int i = 0; i < listeners.length; i++ ) {
624 listeners[i].onPostLoad(postLoadEvent);
625 }
626
627 return result;
628 }
629
630 }