Project

General

Profile

Download (14.4 KB) Statistics
| Branch: | Tag: | Revision:
1
/*
2
 * Hibernate, Relational Persistence for Idiomatic Java
3
 *
4
 * Copyright (c) 2008-2011, Red Hat Inc. 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 Inc.
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
package org.hibernate.proxy;
25

    
26
import java.io.Serializable;
27

    
28
import javax.naming.NamingException;
29

    
30
import org.hibernate.HibernateException;
31
import org.hibernate.LazyInitializationException;
32
import org.hibernate.Session;
33
import org.hibernate.SessionException;
34
import org.hibernate.TransientObjectException;
35
import org.hibernate.engine.spi.EntityKey;
36
import org.hibernate.engine.spi.SessionFactoryImplementor;
37
import org.hibernate.engine.spi.SessionImplementor;
38
import org.hibernate.internal.SessionFactoryRegistry;
39
import org.hibernate.persister.entity.EntityPersister;
40
import org.jboss.logging.Logger;
41

    
42
import eu.etaxonomy.cdm.api.application.CdmApplicationRemoteConfiguration;
43
import eu.etaxonomy.cdm.cache.ProxyUtils;
44
import eu.etaxonomy.cdm.model.common.CdmBase;
45
import eu.etaxonomy.taxeditor.service.ICachedCommonService;
46

    
47
/**
48
 * Convenience base class for lazy initialization handlers.  Centralizes the basic plumbing of doing lazy
49
 * initialization freeing subclasses to acts as essentially adapters to their intended entity mode and/or
50
 * proxy generation strategy.
51
 *
52
 * @author Gavin King
53
 */
54
public abstract class AbstractLazyInitializer implements LazyInitializer {
55
	private static final Logger log = Logger.getLogger( AbstractLazyInitializer.class );
56

    
57
	private String entityName;
58
	private Serializable id;
59
	private Object target;
60
	private boolean initialized;
61
	private boolean readOnly;
62
	private boolean unwrap;
63
	private transient SessionImplementor session;
64
	private Boolean readOnlyBeforeAttachedToSession;
65

    
66
	private String sessionFactoryUuid;
67
	private boolean specjLazyLoad = false;
68

    
69
	/**
70
	 * For serialization from the non-pojo initializers (HHH-3309)
71
	 */
72
	protected AbstractLazyInitializer() {
73
	}
74

    
75
	/**
76
	 * Main constructor.
77
	 *
78
	 * @param entityName The name of the entity being proxied.
79
	 * @param id The identifier of the entity being proxied.
80
	 * @param session The session owning the proxy.
81
	 */
82
	protected AbstractLazyInitializer(String entityName, Serializable id, SessionImplementor session) {
83
		this.entityName = entityName;
84
		this.id = id;
85
		// initialize other fields depending on session state
86
		if ( session == null ) {
87
			unsetSession();
88
		}
89
		else {
90
			setSession( session );
91
		}
92
	}
93

    
94
	@Override
95
	public final String getEntityName() {
96
		return entityName;
97
	}
98

    
99
	@Override
100
	public final Serializable getIdentifier() {
101
		return id;
102
	}
103

    
104
	@Override
105
	public final void setIdentifier(Serializable id) {
106
		this.id = id;
107
	}
108

    
109
	@Override
110
	public final boolean isUninitialized() {
111
		return !initialized;
112
	}
113

    
114
	@Override
115
	public final SessionImplementor getSession() {
116
		return session;
117
	}
118

    
119
	@Override
120
	public final void setSession(SessionImplementor s) throws HibernateException {
121
		if ( s != session ) {
122
			// check for s == null first, since it is least expensive
123
			if ( s == null ) {
124
				unsetSession();
125
			}
126
			else if ( isConnectedToSession() ) {
127
				//TODO: perhaps this should be some other RuntimeException...
128
				throw new HibernateException( "illegally attempted to associate a proxy with two open Sessions" );
129
			}
130
			else {
131
				// s != null
132
				session = s;
133
				if ( readOnlyBeforeAttachedToSession == null ) {
134
					// use the default read-only/modifiable setting
135
					final EntityPersister persister = s.getFactory().getEntityPersister( entityName );
136
					setReadOnly( s.getPersistenceContext().isDefaultReadOnly() || !persister.isMutable() );
137
				}
138
				else {
139
					// use the read-only/modifiable setting indicated during deserialization
140
					setReadOnly( readOnlyBeforeAttachedToSession.booleanValue() );
141
					readOnlyBeforeAttachedToSession = null;
142
				}
143
			}
144
		}
145
	}
146

    
147
	private static EntityKey generateEntityKeyOrNull(Serializable id, SessionImplementor s, String entityName) {
148
		if ( id == null || s == null || entityName == null ) {
149
			return null;
150
		}
151
		return s.generateEntityKey( id, s.getFactory().getEntityPersister( entityName ) );
152
	}
153

    
154
	@Override
155
	public final void unsetSession() {
156
		prepareForPossibleSpecialSpecjInitialization();
157
		session = null;
158
		readOnly = false;
159
		readOnlyBeforeAttachedToSession = null;
160
	}
161

    
162
	@Override
163
	public final void initialize() throws HibernateException {
164
		// In remoting we are sure that session is null
165
		// both when using property paths and switching off conversations
166
		if(session == null && remoting) {
167
			remoteInitialize();
168
		}
169
		if ( !initialized ) {
170
			if ( specjLazyLoad ) {
171
				specialSpecjInitialization();
172
			}
173
			else if ( session == null ) {
174
				throw new LazyInitializationException( "could not initialize proxy - no Session" );
175
			}
176
			else if ( !session.isOpen() ) {
177
				throw new LazyInitializationException( "could not initialize proxy - the owning Session was closed" );
178
			}
179
			else if ( !session.isConnected() ) {
180
				throw new LazyInitializationException( "could not initialize proxy - the owning Session is disconnected" );
181
			}
182
			else {
183
				target = session.immediateLoad( entityName, id );
184
				initialized = true;
185
				checkTargetState();
186
			}
187
		}
188
		else {
189
			checkTargetState();
190
		}
191
	}
192

    
193
	protected void specialSpecjInitialization() {
194
		if ( session == null ) {
195
			//we have a detached collection thats set to null, reattach
196
			if ( sessionFactoryUuid == null ) {
197
				throw new LazyInitializationException( "could not initialize proxy - no Session" );
198
			}
199
			try {
200
				SessionFactoryImplementor sf = (SessionFactoryImplementor)
201
						SessionFactoryRegistry.INSTANCE.getSessionFactory( sessionFactoryUuid );
202
				SessionImplementor session = (SessionImplementor) sf.openSession();
203

    
204
				// TODO: On the next major release, add an
205
				// 'isJTA' or 'getTransactionFactory' method to Session.
206
				/*boolean isJTA = session.getTransactionCoordinator()
207
						.getTransactionContext().getTransactionEnvironment()
208
						.getTransactionFactory()
209
						.compatibleWithJtaSynchronization();
210
				*/
211
				boolean isJTA = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta();
212

    
213
				if ( !isJTA ) {
214
					// Explicitly handle the transactions only if we're not in
215
					// a JTA environment.  A lazy loading temporary session can
216
					// be created even if a current session and transaction are
217
					// open (ex: session.clear() was used).  We must prevent
218
					// multiple transactions.
219
					( ( Session) session ).beginTransaction();
220
				}
221

    
222
				try {
223
					target = session.immediateLoad( entityName, id );
224
				}
225
				finally {
226
					// make sure the just opened temp session gets closed!
227
					try {
228
						if ( !isJTA ) {
229
							( ( Session) session ).getTransaction().commit();
230
						}
231
						( (Session) session ).close();
232
					}
233
					catch (Exception e) {
234
						log.warn( "Unable to close temporary session used to load lazy proxy associated to no session" );
235
					}
236
				}
237
				initialized = true;
238
				checkTargetState();
239
			}
240
			catch (Exception e) {
241
				e.printStackTrace();
242
				throw new LazyInitializationException( e.getMessage() );
243
			}
244
		}
245
		else if ( session.isOpen() && session.isConnected() ) {
246
			target = session.immediateLoad( entityName, id );
247
			initialized = true;
248
			checkTargetState();
249
		}
250
		else {
251
			throw new LazyInitializationException( "could not initialize proxy - Session was closed or disced" );
252
		}
253
	}
254

    
255
	protected void prepareForPossibleSpecialSpecjInitialization() {
256
		if ( session != null ) {
257
			specjLazyLoad = session.getFactory().getSettings().isInitializeLazyStateOutsideTransactionsEnabled();
258

    
259
			if ( specjLazyLoad && sessionFactoryUuid == null ) {
260
				try {
261
					sessionFactoryUuid = (String) session.getFactory().getReference().get( "uuid" ).getContent();
262
				}
263
				catch (NamingException e) {
264
					//not much we can do if this fails...
265
				}
266
			}
267
		}
268
	}
269

    
270
	private void checkTargetState() {
271
		if ( !unwrap ) {
272
			if ( target == null ) {
273
				getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound( entityName, id );
274
			}
275
		}
276
	}
277

    
278
	/**
279
	 * Getter for property 'connectedToSession'.
280
	 *
281
	 * @return Value for property 'connectedToSession'.
282
	 */
283
	protected final boolean isConnectedToSession() {
284
		return getProxyOrNull() != null;
285
	}
286

    
287
	private Object getProxyOrNull() {
288
		final EntityKey entityKey = generateEntityKeyOrNull( getIdentifier(), session, getEntityName() );
289
		if ( entityKey != null && session != null && session.isOpen() ) {
290
			return session.getPersistenceContext().getProxy( entityKey );
291
		}
292
		return null;
293
	}
294

    
295
	@Override
296
	public final Object getImplementation() {
297
		initialize();
298
		return target;
299
	}
300

    
301
	@Override
302
	public final void setImplementation(Object target) {
303
		this.target = target;
304
		initialized = true;
305
	}
306

    
307
	@Override
308
	public final Object getImplementation(SessionImplementor s) throws HibernateException {
309
		final EntityKey entityKey = generateEntityKeyOrNull( getIdentifier(), s, getEntityName() );
310
		return (entityKey == null ? null : s.getPersistenceContext().getEntity( entityKey ));
311
	}
312

    
313
	/**
314
	 * Getter for property 'target'.
315
	 * <p/>
316
	 * Same as {@link #getImplementation()} except that this method will not force initialization.
317
	 *
318
	 * @return Value for property 'target'.
319
	 */
320
	protected final Object getTarget() {
321
		return target;
322
	}
323

    
324
	@Override
325
	public final boolean isReadOnlySettingAvailable() {
326
		return (session != null && !session.isClosed());
327
	}
328

    
329
	private void errorIfReadOnlySettingNotAvailable() {
330
		if ( session == null ) {
331
			throw new TransientObjectException(
332
					"Proxy is detached (i.e, session is null). The read-only/modifiable setting is only accessible when the proxy is associated with an open session."
333
			);
334
		}
335
		if ( session.isClosed() ) {
336
			throw new SessionException(
337
					"Session is closed. The read-only/modifiable setting is only accessible when the proxy is associated with an open session."
338
			);
339
		}
340
	}
341

    
342
	@Override
343
	public final boolean isReadOnly() {
344
		errorIfReadOnlySettingNotAvailable();
345
		return readOnly;
346
	}
347

    
348
	@Override
349
	public final void setReadOnly(boolean readOnly) {
350
		errorIfReadOnlySettingNotAvailable();
351
		// only update if readOnly is different from current setting
352
		if ( this.readOnly != readOnly ) {
353
			final EntityPersister persister = session.getFactory().getEntityPersister( entityName );
354
			if ( !persister.isMutable() && !readOnly ) {
355
				throw new IllegalStateException( "cannot make proxies for immutable entities modifiable" );
356
			}
357
			this.readOnly = readOnly;
358
			if ( initialized ) {
359
				EntityKey key = generateEntityKeyOrNull( getIdentifier(), session, getEntityName() );
360
				if ( key != null && session.getPersistenceContext().containsEntity( key ) ) {
361
					session.getPersistenceContext().setReadOnly( target, readOnly );
362
				}
363
			}
364
		}
365
	}
366

    
367
	/**
368
	 * Get the read-only/modifiable setting that should be put in affect when it is
369
	 * attached to a session.
370
	 * <p/>
371
	 * This method should only be called during serialization when read-only/modifiable setting
372
	 * is not available (i.e., isReadOnlySettingAvailable() == false)
373
	 *
374
	 * @return null, if the default setting should be used;
375
	 *         true, for read-only;
376
	 *         false, for modifiable
377
	 *
378
	 * @throws IllegalStateException if isReadOnlySettingAvailable() == true
379
	 */
380
	protected final Boolean isReadOnlyBeforeAttachedToSession() {
381
		if ( isReadOnlySettingAvailable() ) {
382
			throw new IllegalStateException(
383
					"Cannot call isReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true"
384
			);
385
		}
386
		return readOnlyBeforeAttachedToSession;
387
	}
388

    
389
	/**
390
	 * Set the read-only/modifiable setting that should be put in affect when it is
391
	 * attached to a session.
392
	 * <p/>
393
	 * This method should only be called during deserialization, before associating
394
	 * the proxy with a session.
395
	 *
396
	 * @param readOnlyBeforeAttachedToSession, the read-only/modifiable setting to use when
397
	 * associated with a session; null indicates that the default should be used.
398
	 *
399
	 * @throws IllegalStateException if isReadOnlySettingAvailable() == true
400
	 */
401
	/* package-private */
402
	final void setReadOnlyBeforeAttachedToSession(Boolean readOnlyBeforeAttachedToSession) {
403
		if ( isReadOnlySettingAvailable() ) {
404
			throw new IllegalStateException(
405
					"Cannot call setReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true"
406
			);
407
		}
408
		this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession;
409
	}
410

    
411
	@Override
412
	public boolean isUnwrap() {
413
		return unwrap;
414
	}
415

    
416
	@Override
417
	public void setUnwrap(boolean unwrap) {
418
		this.unwrap = unwrap;
419
	}
420

    
421
	/** Below is section of code which makes remote service calls */
422

    
423
	private static CdmApplicationRemoteConfiguration configuration;
424
	private static boolean remoting = false;
425

    
426
	public static void setConfiguration(CdmApplicationRemoteConfiguration conf) {
427
	    remoting = true;
428
	    configuration = conf;
429
	}
430

    
431

    
432
	private void remoteInitialize() {
433

    
434
		if(!initialized) {
435
			int classid = ((Integer)getIdentifier()).intValue();
436
			log.info("--> Remote Lazy Initializing Object " + getEntityName() + " with id " + classid);
437
			Class clazz;
438
			try {
439
				clazz = Class.forName(getEntityName());
440
			} catch (ClassNotFoundException e) {
441
				throw new HibernateException("Class for " + getEntityName() + " not found", e);
442
			}
443
			if(configuration == null) {
444
				throw new HibernateException("CdmApplicationRemoteConfiguration not initialized (null)");
445
			}
446
			ICachedCommonService cachedCommonService = configuration.getCachedCommonService();
447
			if(cachedCommonService == null) {
448
				throw new HibernateException("commonService not initialized (null)");
449
			}
450

    
451
			CdmBase cdmBase = cachedCommonService.find(clazz,classid);
452
            if(ProxyUtils.isUninitializedProxy(cdmBase)) {
453
                throw new HibernateException("CdmBase Object initialized but is still a proxy");
454
            }
455
			setImplementation(cdmBase);
456

    
457
		}
458
	}
459

    
460
	public static boolean isInitialized(AbstractLazyInitializer obj) {
461
		return obj.initialized;
462
	}
463
}
    (1-1/1)