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 import java.util.Iterator;
29 import java.util.Map;
30
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import org.hibernate.AssertionFailure;
35 import org.hibernate.HibernateException;
36 import org.hibernate.ObjectDeletedException;
37 import org.hibernate.StaleObjectStateException;
38 import org.hibernate.TransientObjectException;
39 import org.hibernate.WrongClassException;
40 import org.hibernate.engine.Cascade;
41 import org.hibernate.engine.CascadingAction;
42 import org.hibernate.engine.EntityEntry;
43 import org.hibernate.engine.EntityKey;
44 import org.hibernate.engine.SessionImplementor;
45 import org.hibernate.engine.Status;
46 import org.hibernate.event.EventSource;
47 import org.hibernate.event.MergeEvent;
48 import org.hibernate.event.MergeEventListener;
49 import org.hibernate.intercept.FieldInterceptionHelper;
50 import org.hibernate.intercept.FieldInterceptor;
51 import org.hibernate.persister.entity.EntityPersister;
52 import org.hibernate.proxy.HibernateProxy;
53 import org.hibernate.proxy.LazyInitializer;
54 import org.hibernate.type.ForeignKeyDirection;
55 import org.hibernate.type.TypeFactory;
56 import org.hibernate.util.IdentityMap;
57
58 /**
59 * Defines the default copy event listener used by hibernate for copying entities
60 * in response to generated copy events.
61 *
62 * @author Gavin King
63 */
64 public class DefaultMergeEventListener extends AbstractSaveEventListener
65 implements MergeEventListener {
66
67 private static final Logger log = LoggerFactory.getLogger(DefaultMergeEventListener.class);
68
69 protected Map getMergeMap(Object anything) {
70 return IdentityMap.invert( (Map) anything );
71 }
72
73 /**
74 * Handle the given merge event.
75 *
76 * @param event The merge event to be handled.
77 * @throws HibernateException
78 */
79 public void onMerge(MergeEvent event) throws HibernateException {
80 Map copyCache = IdentityMap.instantiate(10);
81 onMerge( event, copyCache );
82 for ( Iterator it=copyCache.values().iterator(); it.hasNext(); ) {
83 Object entity = it.next();
84 if ( entity instanceof HibernateProxy ) {
85 entity = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getImplementation();
86 }
87 EntityEntry entry = event.getSession().getPersistenceContext().getEntry( entity );
88 if ( entry == null ) {
89 throw new TransientObjectException(
90 "object references an unsaved transient instance - save the transient instance before merging: " +
91 event.getSession().guessEntityName( entity )
92 );
93 // TODO: cache the entity name somewhere so that it is available to this exception
94 // entity name will not be available for non-POJO entities
95 }
96 if ( entry.getStatus() != Status.MANAGED ) {
97 throw new AssertionFailure( "Merged entity does not have status set to MANAGED; "+entry+" status="+entry.getStatus() );
98 }
99 }
100 }
101
102 /**
103 * Handle the given merge event.
104 *
105 * @param event The merge event to be handled.
106 * @throws HibernateException
107 */
108 public void onMerge(MergeEvent event, Map copyCache) throws HibernateException {
109
110 final EventSource source = event.getSession();
111 final Object original = event.getOriginal();
112
113 if ( original != null ) {
114
115 final Object entity;
116 if ( original instanceof HibernateProxy ) {
117 LazyInitializer li = ( (HibernateProxy) original ).getHibernateLazyInitializer();
118 if ( li.isUninitialized() ) {
119 log.trace("ignoring uninitialized proxy");
120 event.setResult( source.load( li.getEntityName(), li.getIdentifier() ) );
121 return; //EARLY EXIT!
122 }
123 else {
124 entity = li.getImplementation();
125 }
126 }
127 else {
128 entity = original;
129 }
130
131 if ( copyCache.containsKey(entity) &&
132 source.getContextEntityIdentifier( copyCache.get( entity ) ) != null ) {
133 log.trace("already merged");
134 event.setResult(entity);
135 }
136 else {
137 event.setEntity( entity );
138 int entityState = -1;
139
140 // Check the persistence context for an entry relating to this
141 // entity to be merged...
142 EntityEntry entry = source.getPersistenceContext().getEntry( entity );
143 if ( entry == null ) {
144 EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
145 Serializable id = persister.getIdentifier( entity, source.getEntityMode() );
146 if ( id != null ) {
147 EntityKey key = new EntityKey( id, persister, source.getEntityMode() );
148 Object managedEntity = source.getPersistenceContext().getEntity( key );
149 entry = source.getPersistenceContext().getEntry( managedEntity );
150 if ( entry != null ) {
151 // we have specialized case of a detached entity from the
152 // perspective of the merge operation. Specifically, we
153 // have an incoming entity instance which has a corresponding
154 // entry in the current persistence context, but registered
155 // under a different entity instance
156 entityState = DETACHED;
157 }
158 }
159 }
160
161 if ( entityState == -1 ) {
162 entityState = getEntityState( entity, event.getEntityName(), entry, source );
163 }
164
165 switch (entityState) {
166 case DETACHED:
167 entityIsDetached(event, copyCache);
168 break;
169 case TRANSIENT:
170 entityIsTransient(event, copyCache);
171 break;
172 case PERSISTENT:
173 entityIsPersistent(event, copyCache);
174 break;
175 default: //DELETED
176 throw new ObjectDeletedException(
177 "deleted instance passed to merge",
178 null,
179 getLoggableName( event.getEntityName(), entity )
180 );
181 }
182 }
183
184 }
185
186 }
187
188 protected void entityIsPersistent(MergeEvent event, Map copyCache) {
189 log.trace("ignoring persistent instance");
190
191 //TODO: check that entry.getIdentifier().equals(requestedId)
192
193 final Object entity = event.getEntity();
194 final EventSource source = event.getSession();
195 final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
196
197 copyCache.put(entity, entity); //before cascade!
198
199 cascadeOnMerge(source, persister, entity, copyCache);
200 copyValues(persister, entity, entity, source, copyCache);
201
202 event.setResult(entity);
203 }
204
205 protected void entityIsTransient(MergeEvent event, Map copyCache) {
206
207 log.trace("merging transient instance");
208
209 final Object entity = event.getEntity();
210 final EventSource source = event.getSession();
211
212 final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
213 final String entityName = persister.getEntityName();
214
215 final Serializable id = persister.hasIdentifierProperty() ?
216 persister.getIdentifier( entity, source.getEntityMode() ) :
217 null;
218 if ( copyCache.containsKey( entity ) ) {
219 persister.setIdentifier( copyCache.get( entity ), id, source.getEntityMode() );
220 }
221 else {
222 copyCache.put(entity, persister.instantiate( id, source.getEntityMode() ) ); //before cascade!
223 //TODO: should this be Session.instantiate(Persister, ...)?
224 }
225 final Object copy = copyCache.get( entity );
226
227 // cascade first, so that all unsaved objects get their
228 // copy created before we actually copy
229 //cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);
230 super.cascadeBeforeSave(source, persister, entity, copyCache);
231 copyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT);
232
233 //this bit is only *really* absolutely necessary for handling
234 //requestedId, but is also good if we merge multiple object
235 //graphs, since it helps ensure uniqueness
236 final Serializable requestedId = event.getRequestedId();
237 if (requestedId==null) {
238 saveWithGeneratedId( copy, entityName, copyCache, source, false );
239 }
240 else {
241 saveWithRequestedId( copy, requestedId, entityName, copyCache, source );
242 }
243
244 // cascade first, so that all unsaved objects get their
245 // copy created before we actually copy
246 super.cascadeAfterSave(source, persister, entity, copyCache);
247 copyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.FOREIGN_KEY_TO_PARENT);
248
249 event.setResult(copy);
250
251 }
252
253 protected void entityIsDetached(MergeEvent event, Map copyCache) {
254
255 log.trace("merging detached instance");
256
257 final Object entity = event.getEntity();
258 final EventSource source = event.getSession();
259
260 final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
261 final String entityName = persister.getEntityName();
262
263 Serializable id = event.getRequestedId();
264 if ( id == null ) {
265 id = persister.getIdentifier( entity, source.getEntityMode() );
266 }
267 else {
268 // check that entity id = requestedId
269 Serializable entityId = persister.getIdentifier( entity, source.getEntityMode() );
270 if ( !persister.getIdentifierType().isEqual( id, entityId, source.getEntityMode(), source.getFactory() ) ) {
271 throw new HibernateException( "merge requested with id not matching id of passed entity" );
272 }
273 }
274
275 String previousFetchProfile = source.getFetchProfile();
276 source.setFetchProfile("merge");
277 //we must clone embedded composite identifiers, or
278 //we will get back the same instance that we pass in
279 final Serializable clonedIdentifier = (Serializable) persister.getIdentifierType()
280 .deepCopy( id, source.getEntityMode(), source.getFactory() );
281 final Object result = source.get(entityName, clonedIdentifier);
282 source.setFetchProfile(previousFetchProfile);
283
284 if ( result == null ) {
285 //TODO: we should throw an exception if we really *know* for sure
286 // that this is a detached instance, rather than just assuming
287 //throw new StaleObjectStateException(entityName, id);
288
289 // we got here because we assumed that an instance
290 // with an assigned id was detached, when it was
291 // really persistent
292 entityIsTransient(event, copyCache);
293 }
294 else {
295 copyCache.put(entity, result); //before cascade!
296
297 final Object target = source.getPersistenceContext().unproxy(result);
298 if ( target == entity ) {
299 throw new AssertionFailure("entity was not detached");
300 }
301 else if ( !source.getEntityName(target).equals(entityName) ) {
302 throw new WrongClassException(
303 "class of the given object did not match class of persistent copy",
304 event.getRequestedId(),
305 entityName
306 );
307 }
308 else if ( isVersionChanged( entity, source, persister, target ) ) {
309 if ( source.getFactory().getStatistics().isStatisticsEnabled() ) {
310 source.getFactory().getStatisticsImplementor()
311 .optimisticFailure( entityName );
312 }
313 throw new StaleObjectStateException( entityName, id );
314 }
315
316 // cascade first, so that all unsaved objects get their
317 // copy created before we actually copy
318 cascadeOnMerge(source, persister, entity, copyCache);
319 copyValues(persister, entity, target, source, copyCache);
320
321 //copyValues works by reflection, so explicitly mark the entity instance dirty
322 markInterceptorDirty( entity, target );
323
324 event.setResult(result);
325 }
326
327 }
328
329 private void markInterceptorDirty(final Object entity, final Object target) {
330 if ( FieldInterceptionHelper.isInstrumented( entity ) ) {
331 FieldInterceptor interceptor = FieldInterceptionHelper.extractFieldInterceptor( target );
332 if ( interceptor != null ) {
333 interceptor.dirty();
334 }
335 }
336 }
337
338 private boolean isVersionChanged(Object entity, EventSource source, EntityPersister persister, Object target) {
339 if ( ! persister.isVersioned() ) {
340 return false;
341 }
342 // for merging of versioned entities, we consider the version having
343 // been changed only when:
344 // 1) the two version values are different;
345 // *AND*
346 // 2) The target actually represents database state!
347 //
348 // This second condition is a special case which allows
349 // an entity to be merged during the same transaction
350 // (though during a seperate operation) in which it was
351 // originally persisted/saved
352 boolean changed = ! persister.getVersionType().isSame(
353 persister.getVersion( target, source.getEntityMode() ),
354 persister.getVersion( entity, source.getEntityMode() ),
355 source.getEntityMode()
356 );
357
358 // TODO : perhaps we should additionally require that the incoming entity
359 // version be equivalent to the defined unsaved-value?
360 return changed && existsInDatabase( target, source, persister );
361 }
362
363 private boolean existsInDatabase(Object entity, EventSource source, EntityPersister persister) {
364 EntityEntry entry = source.getPersistenceContext().getEntry( entity );
365 if ( entry == null ) {
366 Serializable id = persister.getIdentifier( entity, source.getEntityMode() );
367 if ( id != null ) {
368 EntityKey key = new EntityKey( id, persister, source.getEntityMode() );
369 Object managedEntity = source.getPersistenceContext().getEntity( key );
370 entry = source.getPersistenceContext().getEntry( managedEntity );
371 }
372 }
373
374 if ( entry == null ) {
375 // perhaps this should be an exception since it is only ever used
376 // in the above method?
377 return false;
378 }
379 else {
380 return entry.isExistsInDatabase();
381 }
382 }
383
384 protected void copyValues(
385 final EntityPersister persister,
386 final Object entity,
387 final Object target,
388 final SessionImplementor source,
389 final Map copyCache
390 ) {
391
392 final Object[] copiedValues = TypeFactory.replace(
393 persister.getPropertyValues( entity, source.getEntityMode() ),
394 persister.getPropertyValues( target, source.getEntityMode() ),
395 persister.getPropertyTypes(),
396 source,
397 target,
398 copyCache
399 );
400
401 persister.setPropertyValues( target, copiedValues, source.getEntityMode() );
402 }
403
404 protected void copyValues(
405 final EntityPersister persister,
406 final Object entity,
407 final Object target,
408 final SessionImplementor source,
409 final Map copyCache,
410 final ForeignKeyDirection foreignKeyDirection) {
411
412 final Object[] copiedValues;
413
414 if ( foreignKeyDirection == ForeignKeyDirection.FOREIGN_KEY_TO_PARENT ) {
415 // this is the second pass through on a merge op, so here we limit the
416 // replacement to associations types (value types were already replaced
417 // during the first pass)
418 copiedValues = TypeFactory.replaceAssociations(
419 persister.getPropertyValues( entity, source.getEntityMode() ),
420 persister.getPropertyValues( target, source.getEntityMode() ),
421 persister.getPropertyTypes(),
422 source,
423 target,
424 copyCache,
425 foreignKeyDirection
426 );
427 }
428 else {
429 copiedValues = TypeFactory.replace(
430 persister.getPropertyValues( entity, source.getEntityMode() ),
431 persister.getPropertyValues( target, source.getEntityMode() ),
432 persister.getPropertyTypes(),
433 source,
434 target,
435 copyCache,
436 foreignKeyDirection
437 );
438 }
439
440 persister.setPropertyValues( target, copiedValues, source.getEntityMode() );
441 }
442
443 /**
444 * Perform any cascades needed as part of this copy event.
445 *
446 * @param source The merge event being processed.
447 * @param persister The persister of the entity being copied.
448 * @param entity The entity being copied.
449 * @param copyCache A cache of already copied instance.
450 */
451 protected void cascadeOnMerge(
452 final EventSource source,
453 final EntityPersister persister,
454 final Object entity,
455 final Map copyCache
456 ) {
457 source.getPersistenceContext().incrementCascadeLevel();
458 try {
459 new Cascade( getCascadeAction(), Cascade.BEFORE_MERGE, source )
460 .cascade(persister, entity, copyCache);
461 }
462 finally {
463 source.getPersistenceContext().decrementCascadeLevel();
464 }
465 }
466
467
468 protected CascadingAction getCascadeAction() {
469 return CascadingAction.MERGE;
470 }
471
472 protected Boolean getAssumedUnsaved() {
473 return Boolean.FALSE;
474 }
475
476 /**
477 * Cascade behavior is redefined by this subclass, disable superclass behavior
478 */
479 protected void cascadeAfterSave(EventSource source, EntityPersister persister, Object entity, Object anything)
480 throws HibernateException {
481 }
482
483 /**
484 * Cascade behavior is redefined by this subclass, disable superclass behavior
485 */
486 protected void cascadeBeforeSave(EventSource source, EntityPersister persister, Object entity, Object anything)
487 throws HibernateException {
488 }
489
490 }