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