isRemoting is no longer based on a bean in the application context, but
[taxeditor.git] / eu.etaxonomy.taxeditor.cdmlib / src / main / java / org / hibernate / proxy / AbstractLazyInitializer.java
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 import java.util.Set;
28
29 import javax.naming.NamingException;
30
31 import org.hibernate.HibernateException;
32 import org.hibernate.LazyInitializationException;
33 import org.hibernate.Session;
34 import org.hibernate.SessionException;
35 import org.hibernate.TransientObjectException;
36 import org.hibernate.collection.internal.AbstractPersistentCollection;
37 import org.hibernate.engine.spi.EntityKey;
38 import org.hibernate.engine.spi.SessionFactoryImplementor;
39 import org.hibernate.engine.spi.SessionImplementor;
40 import org.hibernate.internal.SessionFactoryRegistry;
41 import org.hibernate.persister.entity.EntityPersister;
42 import org.jboss.logging.Logger;
43 import org.springframework.beans.factory.annotation.Autowire;
44 import org.springframework.beans.factory.annotation.Configurable;
45 import org.springframework.stereotype.Component;
46
47 import eu.etaxonomy.cdm.api.application.CdmApplicationRemoteController;
48 import eu.etaxonomy.cdm.api.application.ICdmApplicationConfiguration;
49 import eu.etaxonomy.cdm.api.service.ICommonService;
50 import eu.etaxonomy.cdm.model.common.CdmBase;
51
52 /**
53 * Convenience base class for lazy initialization handlers. Centralizes the basic plumbing of doing lazy
54 * initialization freeing subclasses to acts as essentially adapters to their intended entity mode and/or
55 * proxy generation strategy.
56 *
57 * @author Gavin King
58 */
59 @Component
60 @Configurable(dependencyCheck = true,autowire = Autowire.BY_TYPE)
61 public abstract class AbstractLazyInitializer implements LazyInitializer {
62 private static final Logger log = Logger.getLogger( AbstractLazyInitializer.class );
63
64 private String entityName;
65 private Serializable id;
66 private Object target;
67 private boolean initialized;
68 private boolean readOnly;
69 private boolean unwrap;
70 private transient SessionImplementor session;
71 private Boolean readOnlyBeforeAttachedToSession;
72
73 private String sessionFactoryUuid;
74 private boolean specjLazyLoad = false;
75
76 /**
77 * For serialization from the non-pojo initializers (HHH-3309)
78 */
79 protected AbstractLazyInitializer() {
80 }
81
82 /**
83 * Main constructor.
84 *
85 * @param entityName The name of the entity being proxied.
86 * @param id The identifier of the entity being proxied.
87 * @param session The session owning the proxy.
88 */
89 protected AbstractLazyInitializer(String entityName, Serializable id, SessionImplementor session) {
90 this.entityName = entityName;
91 this.id = id;
92 // initialize other fields depending on session state
93 if ( session == null ) {
94 unsetSession();
95 }
96 else {
97 setSession( session );
98 }
99 }
100
101 @Override
102 public final String getEntityName() {
103 return entityName;
104 }
105
106 @Override
107 public final Serializable getIdentifier() {
108 return id;
109 }
110
111 @Override
112 public final void setIdentifier(Serializable id) {
113 this.id = id;
114 }
115
116 @Override
117 public final boolean isUninitialized() {
118 return !initialized;
119 }
120
121 @Override
122 public final SessionImplementor getSession() {
123 return session;
124 }
125
126 @Override
127 public final void setSession(SessionImplementor s) throws HibernateException {
128 if ( s != session ) {
129 // check for s == null first, since it is least expensive
130 if ( s == null ) {
131 unsetSession();
132 }
133 else if ( isConnectedToSession() ) {
134 //TODO: perhaps this should be some other RuntimeException...
135 throw new HibernateException( "illegally attempted to associate a proxy with two open Sessions" );
136 }
137 else {
138 // s != null
139 session = s;
140 if ( readOnlyBeforeAttachedToSession == null ) {
141 // use the default read-only/modifiable setting
142 final EntityPersister persister = s.getFactory().getEntityPersister( entityName );
143 setReadOnly( s.getPersistenceContext().isDefaultReadOnly() || !persister.isMutable() );
144 }
145 else {
146 // use the read-only/modifiable setting indicated during deserialization
147 setReadOnly( readOnlyBeforeAttachedToSession.booleanValue() );
148 readOnlyBeforeAttachedToSession = null;
149 }
150 }
151 }
152 }
153
154 private static EntityKey generateEntityKeyOrNull(Serializable id, SessionImplementor s, String entityName) {
155 if ( id == null || s == null || entityName == null ) {
156 return null;
157 }
158 return s.generateEntityKey( id, s.getFactory().getEntityPersister( entityName ) );
159 }
160
161 @Override
162 public final void unsetSession() {
163 prepareForPossibleSpecialSpecjInitialization();
164 session = null;
165 readOnly = false;
166 readOnlyBeforeAttachedToSession = null;
167 }
168
169 @Override
170 public final void initialize() throws HibernateException {
171 // In remoting we are sure that session is null
172 // both when using property paths and switching off conversations
173 if(session == null && remoting) {
174 remoteInitialize();
175 }
176 if ( !initialized ) {
177 if ( specjLazyLoad ) {
178 specialSpecjInitialization();
179 }
180 else if ( session == null ) {
181 throw new LazyInitializationException( "could not initialize proxy - no Session" );
182 }
183 else if ( !session.isOpen() ) {
184 throw new LazyInitializationException( "could not initialize proxy - the owning Session was closed" );
185 }
186 else if ( !session.isConnected() ) {
187 throw new LazyInitializationException( "could not initialize proxy - the owning Session is disconnected" );
188 }
189 else {
190 target = session.immediateLoad( entityName, id );
191 initialized = true;
192 checkTargetState();
193 }
194 }
195 else {
196 checkTargetState();
197 }
198 }
199
200 protected void specialSpecjInitialization() {
201 if ( session == null ) {
202 //we have a detached collection thats set to null, reattach
203 if ( sessionFactoryUuid == null ) {
204 throw new LazyInitializationException( "could not initialize proxy - no Session" );
205 }
206 try {
207 SessionFactoryImplementor sf = (SessionFactoryImplementor)
208 SessionFactoryRegistry.INSTANCE.getSessionFactory( sessionFactoryUuid );
209 SessionImplementor session = (SessionImplementor) sf.openSession();
210
211 // TODO: On the next major release, add an
212 // 'isJTA' or 'getTransactionFactory' method to Session.
213 boolean isJTA = session.getTransactionCoordinator()
214 .getTransactionContext().getTransactionEnvironment()
215 .getTransactionFactory()
216 .compatibleWithJtaSynchronization();
217
218 if ( !isJTA ) {
219 // Explicitly handle the transactions only if we're not in
220 // a JTA environment. A lazy loading temporary session can
221 // be created even if a current session and transaction are
222 // open (ex: session.clear() was used). We must prevent
223 // multiple transactions.
224 ( ( Session) session ).beginTransaction();
225 }
226
227 try {
228 target = session.immediateLoad( entityName, id );
229 }
230 finally {
231 // make sure the just opened temp session gets closed!
232 try {
233 if ( !isJTA ) {
234 ( ( Session) session ).getTransaction().commit();
235 }
236 ( (Session) session ).close();
237 }
238 catch (Exception e) {
239 log.warn( "Unable to close temporary session used to load lazy proxy associated to no session" );
240 }
241 }
242 initialized = true;
243 checkTargetState();
244 }
245 catch (Exception e) {
246 e.printStackTrace();
247 throw new LazyInitializationException( e.getMessage() );
248 }
249 }
250 else if ( session.isOpen() && session.isConnected() ) {
251 target = session.immediateLoad( entityName, id );
252 initialized = true;
253 checkTargetState();
254 }
255 else {
256 throw new LazyInitializationException( "could not initialize proxy - Session was closed or disced" );
257 }
258 }
259
260 protected void prepareForPossibleSpecialSpecjInitialization() {
261 if ( session != null ) {
262 specjLazyLoad = session.getFactory().getSettings().isInitializeLazyStateOutsideTransactionsEnabled();
263
264 if ( specjLazyLoad && sessionFactoryUuid == null ) {
265 try {
266 sessionFactoryUuid = (String) session.getFactory().getReference().get( "uuid" ).getContent();
267 }
268 catch (NamingException e) {
269 //not much we can do if this fails...
270 }
271 }
272 }
273 }
274
275 private void checkTargetState() {
276 if ( !unwrap ) {
277 if ( target == null ) {
278 getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound( entityName, id );
279 }
280 }
281 }
282
283 /**
284 * Getter for property 'connectedToSession'.
285 *
286 * @return Value for property 'connectedToSession'.
287 */
288 protected final boolean isConnectedToSession() {
289 return getProxyOrNull() != null;
290 }
291
292 private Object getProxyOrNull() {
293 final EntityKey entityKey = generateEntityKeyOrNull( getIdentifier(), session, getEntityName() );
294 if ( entityKey != null && session != null && session.isOpen() ) {
295 return session.getPersistenceContext().getProxy( entityKey );
296 }
297 return null;
298 }
299
300 @Override
301 public final Object getImplementation() {
302 initialize();
303 return target;
304 }
305
306 @Override
307 public final void setImplementation(Object target) {
308 this.target = target;
309 initialized = true;
310 }
311
312 @Override
313 public final Object getImplementation(SessionImplementor s) throws HibernateException {
314 final EntityKey entityKey = generateEntityKeyOrNull( getIdentifier(), s, getEntityName() );
315 return (entityKey == null ? null : s.getPersistenceContext().getEntity( entityKey ));
316 }
317
318 /**
319 * Getter for property 'target'.
320 * <p/>
321 * Same as {@link #getImplementation()} except that this method will not force initialization.
322 *
323 * @return Value for property 'target'.
324 */
325 protected final Object getTarget() {
326 return target;
327 }
328
329 @Override
330 public final boolean isReadOnlySettingAvailable() {
331 return (session != null && !session.isClosed());
332 }
333
334 private void errorIfReadOnlySettingNotAvailable() {
335 if ( session == null ) {
336 throw new TransientObjectException(
337 "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."
338 );
339 }
340 if ( session.isClosed() ) {
341 throw new SessionException(
342 "Session is closed. The read-only/modifiable setting is only accessible when the proxy is associated with an open session."
343 );
344 }
345 }
346
347 @Override
348 public final boolean isReadOnly() {
349 errorIfReadOnlySettingNotAvailable();
350 return readOnly;
351 }
352
353 @Override
354 public final void setReadOnly(boolean readOnly) {
355 errorIfReadOnlySettingNotAvailable();
356 // only update if readOnly is different from current setting
357 if ( this.readOnly != readOnly ) {
358 final EntityPersister persister = session.getFactory().getEntityPersister( entityName );
359 if ( !persister.isMutable() && !readOnly ) {
360 throw new IllegalStateException( "cannot make proxies for immutable entities modifiable" );
361 }
362 this.readOnly = readOnly;
363 if ( initialized ) {
364 EntityKey key = generateEntityKeyOrNull( getIdentifier(), session, getEntityName() );
365 if ( key != null && session.getPersistenceContext().containsEntity( key ) ) {
366 session.getPersistenceContext().setReadOnly( target, readOnly );
367 }
368 }
369 }
370 }
371
372 /**
373 * Get the read-only/modifiable setting that should be put in affect when it is
374 * attached to a session.
375 * <p/>
376 * This method should only be called during serialization when read-only/modifiable setting
377 * is not available (i.e., isReadOnlySettingAvailable() == false)
378 *
379 * @return null, if the default setting should be used;
380 * true, for read-only;
381 * false, for modifiable
382 *
383 * @throws IllegalStateException if isReadOnlySettingAvailable() == true
384 */
385 protected final Boolean isReadOnlyBeforeAttachedToSession() {
386 if ( isReadOnlySettingAvailable() ) {
387 throw new IllegalStateException(
388 "Cannot call isReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true"
389 );
390 }
391 return readOnlyBeforeAttachedToSession;
392 }
393
394 /**
395 * Set the read-only/modifiable setting that should be put in affect when it is
396 * attached to a session.
397 * <p/>
398 * This method should only be called during deserialization, before associating
399 * the proxy with a session.
400 *
401 * @param readOnlyBeforeAttachedToSession, the read-only/modifiable setting to use when
402 * associated with a session; null indicates that the default should be used.
403 *
404 * @throws IllegalStateException if isReadOnlySettingAvailable() == true
405 */
406 /* package-private */
407 final void setReadOnlyBeforeAttachedToSession(Boolean readOnlyBeforeAttachedToSession) {
408 if ( isReadOnlySettingAvailable() ) {
409 throw new IllegalStateException(
410 "Cannot call setReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true"
411 );
412 }
413 this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession;
414 }
415
416 @Override
417 public boolean isUnwrap() {
418 return unwrap;
419 }
420
421 @Override
422 public void setUnwrap(boolean unwrap) {
423 this.unwrap = unwrap;
424 }
425
426 /** Below is section of code which makes remote service calls */
427
428 private static ICdmApplicationConfiguration configuration;
429 private static boolean remoting = false;
430
431 public static void setConfiguration(ICdmApplicationConfiguration conf) {
432 configuration = conf;
433
434 if(conf instanceof CdmApplicationRemoteController) {
435 remoting = true;
436 } else {
437 remoting = false;
438 }
439 }
440
441
442 private void remoteInitialize() {
443
444 if(!initialized) {
445 int classid = ((Integer)getIdentifier()).intValue();
446 log.debug("--> Remote Lazy Initializing" + getEntityName() + " with id " + classid);
447 Class clazz;
448 try {
449 clazz = (Class<? extends CdmBase>) Class.forName(getEntityName());
450 } catch (ClassNotFoundException e) {
451 throw new HibernateException("Class for " + getEntityName() + " not found", e);
452 }
453 if(configuration == null) {
454 throw new HibernateException("CdmApplicationRemoteConfiguration not initialized (null)");
455 }
456 ICommonService commonService = configuration.getCommonService();
457 if(commonService == null) {
458 throw new HibernateException("commonService not initialized (null)");
459 }
460
461 CdmBase cdmBase = CdmBase.deproxy(commonService.find(clazz,classid),clazz);
462 setImplementation(cdmBase);
463
464 }
465 }
466
467 public static boolean isInitialized(AbstractLazyInitializer obj) {
468 return obj.initialized;
469 }
470 }