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
32 import org.hibernate.AssertionFailure;
33 import org.hibernate.EntityMode;
34 import org.hibernate.HibernateException;
35 import org.hibernate.LockMode;
36 import org.hibernate.PersistentObjectException;
37 import org.hibernate.TransientObjectException;
38 import org.hibernate.classic.Lifecycle;
39 import org.hibernate.engine.Cascade;
40 import org.hibernate.engine.CascadingAction;
41 import org.hibernate.engine.EntityEntry;
42 import org.hibernate.engine.EntityKey;
43 import org.hibernate.engine.SessionFactoryImplementor;
44 import org.hibernate.engine.SessionImplementor;
45 import org.hibernate.engine.Status;
46 import org.hibernate.event.EventSource;
47 import org.hibernate.event.SaveOrUpdateEvent;
48 import org.hibernate.event.SaveOrUpdateEventListener;
49 import org.hibernate.persister.entity.EntityPersister;
50 import org.hibernate.pretty.MessageHelper;
51 import org.hibernate.proxy.HibernateProxy;
52
53 /**
54 * Defines the default listener used by Hibernate for handling save-update
55 * events.
56 *
57 * @author Steve Ebersole
58 * @author Gavin King
59 */
60 public class DefaultSaveOrUpdateEventListener extends AbstractSaveEventListener implements SaveOrUpdateEventListener {
61
62 private static final Logger log = LoggerFactory.getLogger( DefaultSaveOrUpdateEventListener.class );
63
64 /**
65 * Handle the given update event.
66 *
67 * @param event The update event to be handled.
68 */
69 public void onSaveOrUpdate(SaveOrUpdateEvent event) {
70 final SessionImplementor source = event.getSession();
71 final Object object = event.getObject();
72 final Serializable requestedId = event.getRequestedId();
73
74 if ( requestedId != null ) {
75 //assign the requested id to the proxy, *before*
76 //reassociating the proxy
77 if ( object instanceof HibernateProxy ) {
78 ( ( HibernateProxy ) object ).getHibernateLazyInitializer().setIdentifier( requestedId );
79 }
80 }
81
82 if ( reassociateIfUninitializedProxy( object, source ) ) {
83 log.trace( "reassociated uninitialized proxy" );
84 // an uninitialized proxy, noop, don't even need to
85 // return an id, since it is never a save()
86 }
87 else {
88 //initialize properties of the event:
89 final Object entity = source.getPersistenceContext().unproxyAndReassociate( object );
90 event.setEntity( entity );
91 event.setEntry( source.getPersistenceContext().getEntry( entity ) );
92 //return the id in the event object
93 event.setResultId( performSaveOrUpdate( event ) );
94 }
95
96 }
97
98 protected boolean reassociateIfUninitializedProxy(Object object, SessionImplementor source) {
99 return source.getPersistenceContext().reassociateIfUninitializedProxy( object );
100 }
101
102 protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event) {
103 int entityState = getEntityState(
104 event.getEntity(),
105 event.getEntityName(),
106 event.getEntry(),
107 event.getSession()
108 );
109
110 switch ( entityState ) {
111 case DETACHED:
112 entityIsDetached( event );
113 return null;
114 case PERSISTENT:
115 return entityIsPersistent( event );
116 default: //TRANSIENT or DELETED
117 return entityIsTransient( event );
118 }
119 }
120
121 protected Serializable entityIsPersistent(SaveOrUpdateEvent event) throws HibernateException {
122 log.trace( "ignoring persistent instance" );
123
124 EntityEntry entityEntry = event.getEntry();
125 if ( entityEntry == null ) {
126 throw new AssertionFailure( "entity was transient or detached" );
127 }
128 else {
129
130 if ( entityEntry.getStatus() == Status.DELETED ) {
131 throw new AssertionFailure( "entity was deleted" );
132 }
133
134 final SessionFactoryImplementor factory = event.getSession().getFactory();
135
136 Serializable requestedId = event.getRequestedId();
137
138 Serializable savedId;
139 if ( requestedId == null ) {
140 savedId = entityEntry.getId();
141 }
142 else {
143
144 final boolean isEqual = !entityEntry.getPersister().getIdentifierType()
145 .isEqual( requestedId, entityEntry.getId(), event.getSession().getEntityMode(), factory );
146
147 if ( isEqual ) {
148 throw new PersistentObjectException(
149 "object passed to save() was already persistent: " +
150 MessageHelper.infoString( entityEntry.getPersister(), requestedId, factory )
151 );
152 }
153
154 savedId = requestedId;
155
156 }
157
158 if ( log.isTraceEnabled() ) {
159 log.trace(
160 "object already associated with session: " +
161 MessageHelper.infoString( entityEntry.getPersister(), savedId, factory )
162 );
163 }
164
165 return savedId;
166
167 }
168 }
169
170 /**
171 * The given save-update event named a transient entity.
172 * <p/>
173 * Here, we will perform the save processing.
174 *
175 * @param event The save event to be handled.
176 *
177 * @return The entity's identifier after saving.
178 */
179 protected Serializable entityIsTransient(SaveOrUpdateEvent event) {
180
181 log.trace( "saving transient instance" );
182
183 final EventSource source = event.getSession();
184
185 EntityEntry entityEntry = event.getEntry();
186 if ( entityEntry != null ) {
187 if ( entityEntry.getStatus() == Status.DELETED ) {
188 source.forceFlush( entityEntry );
189 }
190 else {
191 throw new AssertionFailure( "entity was persistent" );
192 }
193 }
194
195 Serializable id = saveWithGeneratedOrRequestedId( event );
196
197 source.getPersistenceContext().reassociateProxy( event.getObject(), id );
198
199 return id;
200 }
201
202 /**
203 * Save the transient instance, assigning the right identifier
204 *
205 * @param event The initiating event.
206 *
207 * @return The entity's identifier value after saving.
208 */
209 protected Serializable saveWithGeneratedOrRequestedId(SaveOrUpdateEvent event) {
210 return saveWithGeneratedId(
211 event.getEntity(),
212 event.getEntityName(),
213 null,
214 event.getSession(),
215 true
216 );
217 }
218
219 /**
220 * The given save-update event named a detached entity.
221 * <p/>
222 * Here, we will perform the update processing.
223 *
224 * @param event The update event to be handled.
225 */
226 protected void entityIsDetached(SaveOrUpdateEvent event) {
227
228 log.trace( "updating detached instance" );
229
230
231 if ( event.getSession().getPersistenceContext().isEntryFor( event.getEntity() ) ) {
232 //TODO: assertion only, could be optimized away
233 throw new AssertionFailure( "entity was persistent" );
234 }
235
236 Object entity = event.getEntity();
237
238 EntityPersister persister = event.getSession().getEntityPersister( event.getEntityName(), entity );
239
240 event.setRequestedId(
241 getUpdateId(
242 entity, persister, event.getRequestedId(), event.getSession().getEntityMode()
243 )
244 );
245
246 performUpdate( event, entity, persister );
247
248 }
249
250 /**
251 * Determine the id to use for updating.
252 *
253 * @param entity The entity.
254 * @param persister The entity persister
255 * @param requestedId The requested identifier
256 * @param entityMode The entity mode.
257 *
258 * @return The id.
259 *
260 * @throws TransientObjectException If the entity is considered transient.
261 */
262 protected Serializable getUpdateId(
263 Object entity,
264 EntityPersister persister,
265 Serializable requestedId,
266 EntityMode entityMode) {
267 // use the id assigned to the instance
268 Serializable id = persister.getIdentifier( entity, entityMode );
269 if ( id == null ) {
270 // assume this is a newly instantiated transient object
271 // which should be saved rather than updated
272 throw new TransientObjectException(
273 "The given object has a null identifier: " +
274 persister.getEntityName()
275 );
276 }
277 else {
278 return id;
279 }
280
281 }
282
283 protected void performUpdate(
284 SaveOrUpdateEvent event,
285 Object entity,
286 EntityPersister persister) throws HibernateException {
287
288 if ( !persister.isMutable() ) {
289 log.trace( "immutable instance passed to doUpdate(), locking" );
290 reassociate( event, entity, event.getRequestedId(), persister );
291 }
292 else {
293
294 if ( log.isTraceEnabled() ) {
295 log.trace(
296 "updating " +
297 MessageHelper.infoString(
298 persister, event.getRequestedId(), event.getSession().getFactory()
299 )
300 );
301 }
302
303 final EventSource source = event.getSession();
304
305 EntityKey key = new EntityKey( event.getRequestedId(), persister, source.getEntityMode() );
306
307 source.getPersistenceContext().checkUniqueness( key, entity );
308
309 if ( invokeUpdateLifecycle( entity, persister, source ) ) {
310 reassociate( event, event.getObject(), event.getRequestedId(), persister );
311 return;
312 }
313
314 // this is a transient object with existing persistent state not loaded by the session
315
316 new OnUpdateVisitor( source, event.getRequestedId(), entity ).process( entity, persister );
317
318 //TODO: put this stuff back in to read snapshot from
319 // the second-level cache (needs some extra work)
320 /*Object[] cachedState = null;
321
322 if ( persister.hasCache() ) {
323 CacheEntry entry = (CacheEntry) persister.getCache()
324 .get( event.getRequestedId(), source.getTimestamp() );
325 cachedState = entry==null ?
326 null :
327 entry.getState(); //TODO: half-assemble this stuff
328 }*/
329
330 source.getPersistenceContext().addEntity(
331 entity,
332 Status.MANAGED,
333 null, //cachedState,
334 key,
335 persister.getVersion( entity, source.getEntityMode() ),
336 LockMode.NONE,
337 true,
338 persister,
339 false,
340 true //assume true, since we don't really know, and it doesn't matter
341 );
342
343 persister.afterReassociate( entity, source );
344
345 if ( log.isTraceEnabled() ) {
346 log.trace(
347 "updating " +
348 MessageHelper.infoString( persister, event.getRequestedId(), source.getFactory() )
349 );
350 }
351
352 cascadeOnUpdate( event, persister, entity );
353
354 }
355 }
356
357 protected boolean invokeUpdateLifecycle(Object entity, EntityPersister persister, EventSource source) {
358 if ( persister.implementsLifecycle( source.getEntityMode() ) ) {
359 log.debug( "calling onUpdate()" );
360 if ( ( ( Lifecycle ) entity ).onUpdate( source ) ) {
361 log.debug( "update vetoed by onUpdate()" );
362 return true;
363 }
364 }
365 return false;
366 }
367
368 /**
369 * Handles the calls needed to perform cascades as part of an update request
370 * for the given entity.
371 *
372 * @param event The event currently being processed.
373 * @param persister The defined persister for the entity being updated.
374 * @param entity The entity being updated.
375 */
376 private void cascadeOnUpdate(SaveOrUpdateEvent event, EntityPersister persister, Object entity) {
377 EventSource source = event.getSession();
378 source.getPersistenceContext().incrementCascadeLevel();
379 try {
380 new Cascade( CascadingAction.SAVE_UPDATE, Cascade.AFTER_UPDATE, source )
381 .cascade( persister, entity );
382 }
383 finally {
384 source.getPersistenceContext().decrementCascadeLevel();
385 }
386 }
387
388 protected CascadingAction getCascadeAction() {
389 return CascadingAction.SAVE_UPDATE;
390 }
391 }