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.CdmApplicationRemoteController;
|
43
|
import eu.etaxonomy.cdm.api.application.ICdmApplicationConfiguration;
|
44
|
import eu.etaxonomy.cdm.api.service.ICommonService;
|
45
|
import eu.etaxonomy.cdm.model.common.CdmBase;
|
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
|
if ( !isJTA ) {
|
212
|
// Explicitly handle the transactions only if we're not in
|
213
|
// a JTA environment. A lazy loading temporary session can
|
214
|
// be created even if a current session and transaction are
|
215
|
// open (ex: session.clear() was used). We must prevent
|
216
|
// multiple transactions.
|
217
|
( ( Session) session ).beginTransaction();
|
218
|
}
|
219
|
|
220
|
try {
|
221
|
target = session.immediateLoad( entityName, id );
|
222
|
}
|
223
|
finally {
|
224
|
// make sure the just opened temp session gets closed!
|
225
|
try {
|
226
|
if ( !isJTA ) {
|
227
|
( ( Session) session ).getTransaction().commit();
|
228
|
}
|
229
|
( (Session) session ).close();
|
230
|
}
|
231
|
catch (Exception e) {
|
232
|
log.warn( "Unable to close temporary session used to load lazy proxy associated to no session" );
|
233
|
}
|
234
|
}
|
235
|
initialized = true;
|
236
|
checkTargetState();
|
237
|
}
|
238
|
catch (Exception e) {
|
239
|
e.printStackTrace();
|
240
|
throw new LazyInitializationException( e.getMessage() );
|
241
|
}
|
242
|
}
|
243
|
else if ( session.isOpen() && session.isConnected() ) {
|
244
|
target = session.immediateLoad( entityName, id );
|
245
|
initialized = true;
|
246
|
checkTargetState();
|
247
|
}
|
248
|
else {
|
249
|
throw new LazyInitializationException( "could not initialize proxy - Session was closed or disced" );
|
250
|
}
|
251
|
}
|
252
|
|
253
|
protected void prepareForPossibleSpecialSpecjInitialization() {
|
254
|
if ( session != null ) {
|
255
|
specjLazyLoad = session.getFactory().getSettings().isInitializeLazyStateOutsideTransactionsEnabled();
|
256
|
|
257
|
if ( specjLazyLoad && sessionFactoryUuid == null ) {
|
258
|
try {
|
259
|
sessionFactoryUuid = (String) session.getFactory().getReference().get( "uuid" ).getContent();
|
260
|
}
|
261
|
catch (NamingException e) {
|
262
|
//not much we can do if this fails...
|
263
|
}
|
264
|
}
|
265
|
}
|
266
|
}
|
267
|
|
268
|
private void checkTargetState() {
|
269
|
if ( !unwrap ) {
|
270
|
if ( target == null ) {
|
271
|
getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound( entityName, id );
|
272
|
}
|
273
|
}
|
274
|
}
|
275
|
|
276
|
/**
|
277
|
* Getter for property 'connectedToSession'.
|
278
|
*
|
279
|
* @return Value for property 'connectedToSession'.
|
280
|
*/
|
281
|
protected final boolean isConnectedToSession() {
|
282
|
return getProxyOrNull() != null;
|
283
|
}
|
284
|
|
285
|
private Object getProxyOrNull() {
|
286
|
final EntityKey entityKey = generateEntityKeyOrNull( getIdentifier(), session, getEntityName() );
|
287
|
if ( entityKey != null && session != null && session.isOpen() ) {
|
288
|
return session.getPersistenceContext().getProxy( entityKey );
|
289
|
}
|
290
|
return null;
|
291
|
}
|
292
|
|
293
|
@Override
|
294
|
public final Object getImplementation() {
|
295
|
initialize();
|
296
|
return target;
|
297
|
}
|
298
|
|
299
|
@Override
|
300
|
public final void setImplementation(Object target) {
|
301
|
this.target = target;
|
302
|
initialized = true;
|
303
|
}
|
304
|
|
305
|
@Override
|
306
|
public final Object getImplementation(SessionImplementor s) throws HibernateException {
|
307
|
final EntityKey entityKey = generateEntityKeyOrNull( getIdentifier(), s, getEntityName() );
|
308
|
return (entityKey == null ? null : s.getPersistenceContext().getEntity( entityKey ));
|
309
|
}
|
310
|
|
311
|
/**
|
312
|
* Getter for property 'target'.
|
313
|
* <p/>
|
314
|
* Same as {@link #getImplementation()} except that this method will not force initialization.
|
315
|
*
|
316
|
* @return Value for property 'target'.
|
317
|
*/
|
318
|
protected final Object getTarget() {
|
319
|
return target;
|
320
|
}
|
321
|
|
322
|
@Override
|
323
|
public final boolean isReadOnlySettingAvailable() {
|
324
|
return (session != null && !session.isClosed());
|
325
|
}
|
326
|
|
327
|
private void errorIfReadOnlySettingNotAvailable() {
|
328
|
if ( session == null ) {
|
329
|
throw new TransientObjectException(
|
330
|
"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."
|
331
|
);
|
332
|
}
|
333
|
if ( session.isClosed() ) {
|
334
|
throw new SessionException(
|
335
|
"Session is closed. The read-only/modifiable setting is only accessible when the proxy is associated with an open session."
|
336
|
);
|
337
|
}
|
338
|
}
|
339
|
|
340
|
@Override
|
341
|
public final boolean isReadOnly() {
|
342
|
errorIfReadOnlySettingNotAvailable();
|
343
|
return readOnly;
|
344
|
}
|
345
|
|
346
|
@Override
|
347
|
public final void setReadOnly(boolean readOnly) {
|
348
|
errorIfReadOnlySettingNotAvailable();
|
349
|
// only update if readOnly is different from current setting
|
350
|
if ( this.readOnly != readOnly ) {
|
351
|
final EntityPersister persister = session.getFactory().getEntityPersister( entityName );
|
352
|
if ( !persister.isMutable() && !readOnly ) {
|
353
|
throw new IllegalStateException( "cannot make proxies for immutable entities modifiable" );
|
354
|
}
|
355
|
this.readOnly = readOnly;
|
356
|
if ( initialized ) {
|
357
|
EntityKey key = generateEntityKeyOrNull( getIdentifier(), session, getEntityName() );
|
358
|
if ( key != null && session.getPersistenceContext().containsEntity( key ) ) {
|
359
|
session.getPersistenceContext().setReadOnly( target, readOnly );
|
360
|
}
|
361
|
}
|
362
|
}
|
363
|
}
|
364
|
|
365
|
/**
|
366
|
* Get the read-only/modifiable setting that should be put in affect when it is
|
367
|
* attached to a session.
|
368
|
* <p/>
|
369
|
* This method should only be called during serialization when read-only/modifiable setting
|
370
|
* is not available (i.e., isReadOnlySettingAvailable() == false)
|
371
|
*
|
372
|
* @return null, if the default setting should be used;
|
373
|
* true, for read-only;
|
374
|
* false, for modifiable
|
375
|
*
|
376
|
* @throws IllegalStateException if isReadOnlySettingAvailable() == true
|
377
|
*/
|
378
|
protected final Boolean isReadOnlyBeforeAttachedToSession() {
|
379
|
if ( isReadOnlySettingAvailable() ) {
|
380
|
throw new IllegalStateException(
|
381
|
"Cannot call isReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true"
|
382
|
);
|
383
|
}
|
384
|
return readOnlyBeforeAttachedToSession;
|
385
|
}
|
386
|
|
387
|
/**
|
388
|
* Set the read-only/modifiable setting that should be put in affect when it is
|
389
|
* attached to a session.
|
390
|
* <p/>
|
391
|
* This method should only be called during deserialization, before associating
|
392
|
* the proxy with a session.
|
393
|
*
|
394
|
* @param readOnlyBeforeAttachedToSession, the read-only/modifiable setting to use when
|
395
|
* associated with a session; null indicates that the default should be used.
|
396
|
*
|
397
|
* @throws IllegalStateException if isReadOnlySettingAvailable() == true
|
398
|
*/
|
399
|
/* package-private */
|
400
|
final void setReadOnlyBeforeAttachedToSession(Boolean readOnlyBeforeAttachedToSession) {
|
401
|
if ( isReadOnlySettingAvailable() ) {
|
402
|
throw new IllegalStateException(
|
403
|
"Cannot call setReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true"
|
404
|
);
|
405
|
}
|
406
|
this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession;
|
407
|
}
|
408
|
|
409
|
@Override
|
410
|
public boolean isUnwrap() {
|
411
|
return unwrap;
|
412
|
}
|
413
|
|
414
|
@Override
|
415
|
public void setUnwrap(boolean unwrap) {
|
416
|
this.unwrap = unwrap;
|
417
|
}
|
418
|
|
419
|
/** Below is section of code which makes remote service calls */
|
420
|
|
421
|
private static ICdmApplicationConfiguration configuration;
|
422
|
private static boolean remoting = false;
|
423
|
|
424
|
public static void setConfiguration(ICdmApplicationConfiguration conf) {
|
425
|
configuration = conf;
|
426
|
|
427
|
if(conf instanceof CdmApplicationRemoteController) {
|
428
|
remoting = true;
|
429
|
} else {
|
430
|
remoting = false;
|
431
|
}
|
432
|
}
|
433
|
|
434
|
|
435
|
private void remoteInitialize() {
|
436
|
|
437
|
if(!initialized) {
|
438
|
int classid = ((Integer)getIdentifier()).intValue();
|
439
|
log.debug("--> Remote Lazy Initializing" + getEntityName() + " with id " + classid);
|
440
|
Class clazz;
|
441
|
try {
|
442
|
clazz = Class.forName(getEntityName());
|
443
|
} catch (ClassNotFoundException e) {
|
444
|
throw new HibernateException("Class for " + getEntityName() + " not found", e);
|
445
|
}
|
446
|
if(configuration == null) {
|
447
|
throw new HibernateException("CdmApplicationRemoteConfiguration not initialized (null)");
|
448
|
}
|
449
|
ICommonService commonService = configuration.getCommonService();
|
450
|
if(commonService == null) {
|
451
|
throw new HibernateException("commonService not initialized (null)");
|
452
|
}
|
453
|
|
454
|
CdmBase cdmBase = CdmBase.deproxy(commonService.find(clazz,classid),clazz);
|
455
|
setImplementation(cdmBase);
|
456
|
|
457
|
}
|
458
|
}
|
459
|
|
460
|
public static boolean isInitialized(AbstractLazyInitializer obj) {
|
461
|
return obj.initialized;
|
462
|
}
|
463
|
}
|