X-Git-Url: https://dev.e-taxonomy.eu/gitweb/taxeditor.git/blobdiff_plain/59695a56595a8865672a2aac733c23b50e54509f..58696c3534a439d3e9efe70b6c28e632971f83ff:/eu.etaxonomy.taxeditor.cdmlib/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java diff --git a/eu.etaxonomy.taxeditor.cdmlib/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java b/eu.etaxonomy.taxeditor.cdmlib/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java index de085411f..39adf8830 100644 --- a/eu.etaxonomy.taxeditor.cdmlib/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java +++ b/eu.etaxonomy.taxeditor.cdmlib/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java @@ -1,463 +1,470 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.proxy; - -import java.io.Serializable; - -import javax.naming.NamingException; - -import org.hibernate.HibernateException; -import org.hibernate.LazyInitializationException; -import org.hibernate.Session; -import org.hibernate.SessionException; -import org.hibernate.TransientObjectException; -import org.hibernate.engine.spi.EntityKey; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.internal.SessionFactoryRegistry; -import org.hibernate.persister.entity.EntityPersister; -import org.jboss.logging.Logger; - -import eu.etaxonomy.cdm.api.application.CdmApplicationRemoteConfiguration; -import eu.etaxonomy.cdm.cache.ProxyUtils; -import eu.etaxonomy.cdm.model.common.CdmBase; -import eu.etaxonomy.taxeditor.service.ICachedCommonService; - -/** - * Convenience base class for lazy initialization handlers. Centralizes the basic plumbing of doing lazy - * initialization freeing subclasses to acts as essentially adapters to their intended entity mode and/or - * proxy generation strategy. - * - * @author Gavin King - */ -public abstract class AbstractLazyInitializer implements LazyInitializer { - private static final Logger log = Logger.getLogger( AbstractLazyInitializer.class ); - - private String entityName; - private Serializable id; - private Object target; - private boolean initialized; - private boolean readOnly; - private boolean unwrap; - private transient SessionImplementor session; - private Boolean readOnlyBeforeAttachedToSession; - - private String sessionFactoryUuid; - private boolean specjLazyLoad = false; - - /** - * For serialization from the non-pojo initializers (HHH-3309) - */ - protected AbstractLazyInitializer() { - } - - /** - * Main constructor. - * - * @param entityName The name of the entity being proxied. - * @param id The identifier of the entity being proxied. - * @param session The session owning the proxy. - */ - protected AbstractLazyInitializer(String entityName, Serializable id, SessionImplementor session) { - this.entityName = entityName; - this.id = id; - // initialize other fields depending on session state - if ( session == null ) { - unsetSession(); - } - else { - setSession( session ); - } - } - - @Override - public final String getEntityName() { - return entityName; - } - - @Override - public final Serializable getIdentifier() { - return id; - } - - @Override - public final void setIdentifier(Serializable id) { - this.id = id; - } - - @Override - public final boolean isUninitialized() { - return !initialized; - } - - @Override - public final SessionImplementor getSession() { - return session; - } - - @Override - public final void setSession(SessionImplementor s) throws HibernateException { - if ( s != session ) { - // check for s == null first, since it is least expensive - if ( s == null ) { - unsetSession(); - } - else if ( isConnectedToSession() ) { - //TODO: perhaps this should be some other RuntimeException... - throw new HibernateException( "illegally attempted to associate a proxy with two open Sessions" ); - } - else { - // s != null - session = s; - if ( readOnlyBeforeAttachedToSession == null ) { - // use the default read-only/modifiable setting - final EntityPersister persister = s.getFactory().getEntityPersister( entityName ); - setReadOnly( s.getPersistenceContext().isDefaultReadOnly() || !persister.isMutable() ); - } - else { - // use the read-only/modifiable setting indicated during deserialization - setReadOnly( readOnlyBeforeAttachedToSession.booleanValue() ); - readOnlyBeforeAttachedToSession = null; - } - } - } - } - - private static EntityKey generateEntityKeyOrNull(Serializable id, SessionImplementor s, String entityName) { - if ( id == null || s == null || entityName == null ) { - return null; - } - return s.generateEntityKey( id, s.getFactory().getEntityPersister( entityName ) ); - } - - @Override - public final void unsetSession() { - prepareForPossibleSpecialSpecjInitialization(); - session = null; - readOnly = false; - readOnlyBeforeAttachedToSession = null; - } - - @Override - public final void initialize() throws HibernateException { - // In remoting we are sure that session is null - // both when using property paths and switching off conversations - if(session == null && remoting) { - remoteInitialize(); - } - if ( !initialized ) { - if ( specjLazyLoad ) { - specialSpecjInitialization(); - } - else if ( session == null ) { - throw new LazyInitializationException( "could not initialize proxy - no Session" ); - } - else if ( !session.isOpen() ) { - throw new LazyInitializationException( "could not initialize proxy - the owning Session was closed" ); - } - else if ( !session.isConnected() ) { - throw new LazyInitializationException( "could not initialize proxy - the owning Session is disconnected" ); - } - else { - target = session.immediateLoad( entityName, id ); - initialized = true; - checkTargetState(); - } - } - else { - checkTargetState(); - } - } - - protected void specialSpecjInitialization() { - if ( session == null ) { - //we have a detached collection thats set to null, reattach - if ( sessionFactoryUuid == null ) { - throw new LazyInitializationException( "could not initialize proxy - no Session" ); - } - try { - SessionFactoryImplementor sf = (SessionFactoryImplementor) - SessionFactoryRegistry.INSTANCE.getSessionFactory( sessionFactoryUuid ); - SessionImplementor session = (SessionImplementor) sf.openSession(); - - // TODO: On the next major release, add an - // 'isJTA' or 'getTransactionFactory' method to Session. - /*boolean isJTA = session.getTransactionCoordinator() - .getTransactionContext().getTransactionEnvironment() - .getTransactionFactory() - .compatibleWithJtaSynchronization(); - */ - boolean isJTA = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); - - if ( !isJTA ) { - // Explicitly handle the transactions only if we're not in - // a JTA environment. A lazy loading temporary session can - // be created even if a current session and transaction are - // open (ex: session.clear() was used). We must prevent - // multiple transactions. - ( ( Session) session ).beginTransaction(); - } - - try { - target = session.immediateLoad( entityName, id ); - } - finally { - // make sure the just opened temp session gets closed! - try { - if ( !isJTA ) { - ( ( Session) session ).getTransaction().commit(); - } - ( (Session) session ).close(); - } - catch (Exception e) { - log.warn( "Unable to close temporary session used to load lazy proxy associated to no session" ); - } - } - initialized = true; - checkTargetState(); - } - catch (Exception e) { - e.printStackTrace(); - throw new LazyInitializationException( e.getMessage() ); - } - } - else if ( session.isOpen() && session.isConnected() ) { - target = session.immediateLoad( entityName, id ); - initialized = true; - checkTargetState(); - } - else { - throw new LazyInitializationException( "could not initialize proxy - Session was closed or disced" ); - } - } - - protected void prepareForPossibleSpecialSpecjInitialization() { - if ( session != null ) { - specjLazyLoad = session.getFactory().getSettings().isInitializeLazyStateOutsideTransactionsEnabled(); - - if ( specjLazyLoad && sessionFactoryUuid == null ) { - try { - sessionFactoryUuid = (String) session.getFactory().getReference().get( "uuid" ).getContent(); - } - catch (NamingException e) { - //not much we can do if this fails... - } - } - } - } - - private void checkTargetState() { - if ( !unwrap ) { - if ( target == null ) { - getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound( entityName, id ); - } - } - } - - /** - * Getter for property 'connectedToSession'. - * - * @return Value for property 'connectedToSession'. - */ - protected final boolean isConnectedToSession() { - return getProxyOrNull() != null; - } - - private Object getProxyOrNull() { - final EntityKey entityKey = generateEntityKeyOrNull( getIdentifier(), session, getEntityName() ); - if ( entityKey != null && session != null && session.isOpen() ) { - return session.getPersistenceContext().getProxy( entityKey ); - } - return null; - } - - @Override - public final Object getImplementation() { - initialize(); - return target; - } - - @Override - public final void setImplementation(Object target) { - this.target = target; - initialized = true; - } - - @Override - public final Object getImplementation(SessionImplementor s) throws HibernateException { - final EntityKey entityKey = generateEntityKeyOrNull( getIdentifier(), s, getEntityName() ); - return (entityKey == null ? null : s.getPersistenceContext().getEntity( entityKey )); - } - - /** - * Getter for property 'target'. - *

- * Same as {@link #getImplementation()} except that this method will not force initialization. - * - * @return Value for property 'target'. - */ - protected final Object getTarget() { - return target; - } - - @Override - public final boolean isReadOnlySettingAvailable() { - return (session != null && !session.isClosed()); - } - - private void errorIfReadOnlySettingNotAvailable() { - if ( session == null ) { - throw new TransientObjectException( - "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." - ); - } - if ( session.isClosed() ) { - throw new SessionException( - "Session is closed. The read-only/modifiable setting is only accessible when the proxy is associated with an open session." - ); - } - } - - @Override - public final boolean isReadOnly() { - errorIfReadOnlySettingNotAvailable(); - return readOnly; - } - - @Override - public final void setReadOnly(boolean readOnly) { - errorIfReadOnlySettingNotAvailable(); - // only update if readOnly is different from current setting - if ( this.readOnly != readOnly ) { - final EntityPersister persister = session.getFactory().getEntityPersister( entityName ); - if ( !persister.isMutable() && !readOnly ) { - throw new IllegalStateException( "cannot make proxies for immutable entities modifiable" ); - } - this.readOnly = readOnly; - if ( initialized ) { - EntityKey key = generateEntityKeyOrNull( getIdentifier(), session, getEntityName() ); - if ( key != null && session.getPersistenceContext().containsEntity( key ) ) { - session.getPersistenceContext().setReadOnly( target, readOnly ); - } - } - } - } - - /** - * Get the read-only/modifiable setting that should be put in affect when it is - * attached to a session. - *

- * This method should only be called during serialization when read-only/modifiable setting - * is not available (i.e., isReadOnlySettingAvailable() == false) - * - * @return null, if the default setting should be used; - * true, for read-only; - * false, for modifiable - * - * @throws IllegalStateException if isReadOnlySettingAvailable() == true - */ - protected final Boolean isReadOnlyBeforeAttachedToSession() { - if ( isReadOnlySettingAvailable() ) { - throw new IllegalStateException( - "Cannot call isReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true" - ); - } - return readOnlyBeforeAttachedToSession; - } - - /** - * Set the read-only/modifiable setting that should be put in affect when it is - * attached to a session. - *

- * This method should only be called during deserialization, before associating - * the proxy with a session. - * - * @param readOnlyBeforeAttachedToSession, the read-only/modifiable setting to use when - * associated with a session; null indicates that the default should be used. - * - * @throws IllegalStateException if isReadOnlySettingAvailable() == true - */ - /* package-private */ - final void setReadOnlyBeforeAttachedToSession(Boolean readOnlyBeforeAttachedToSession) { - if ( isReadOnlySettingAvailable() ) { - throw new IllegalStateException( - "Cannot call setReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true" - ); - } - this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession; - } - - @Override - public boolean isUnwrap() { - return unwrap; - } - - @Override - public void setUnwrap(boolean unwrap) { - this.unwrap = unwrap; - } - - /** Below is section of code which makes remote service calls */ - - private static CdmApplicationRemoteConfiguration configuration; - private static boolean remoting = false; - - public static void setConfiguration(CdmApplicationRemoteConfiguration conf) { - remoting = true; - configuration = conf; - } - - - private void remoteInitialize() { - - if(!initialized) { - int classid = ((Integer)getIdentifier()).intValue(); - log.info("--> Remote Lazy Initializing Object " + getEntityName() + " with id " + classid); - Class clazz; - try { - clazz = Class.forName(getEntityName()); - } catch (ClassNotFoundException e) { - throw new HibernateException("Class for " + getEntityName() + " not found", e); - } - if(configuration == null) { - throw new HibernateException("CdmApplicationRemoteConfiguration not initialized (null)"); - } - ICachedCommonService cachedCommonService = configuration.getCachedCommonService(); - if(cachedCommonService == null) { - throw new HibernateException("commonService not initialized (null)"); - } - - CdmBase cdmBase = cachedCommonService.find(clazz,classid); - if(ProxyUtils.isUninitializedProxy(cdmBase)) { - throw new HibernateException("CdmBase Object initialized but is still a proxy"); - } - setImplementation(cdmBase); - - } - } - - public static boolean isInitialized(AbstractLazyInitializer obj) { - return obj.initialized; - } -} +/* Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.proxy; + +import java.io.Serializable; + +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.LazyInitializationException; +import org.hibernate.SessionException; +import org.hibernate.TransientObjectException; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.SessionFactoryRegistry; +import org.hibernate.persister.entity.EntityPersister; + +/** + * Convenience base class for lazy initialization handlers. Centralizes the basic plumbing of doing lazy + * initialization freeing subclasses to acts as essentially adapters to their intended entity mode and/or + * proxy generation strategy. + * + * @author Gavin King + */ +public abstract class AbstractLazyInitializer implements LazyInitializer { + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractLazyInitializer.class ); + + private String entityName; + private Serializable id; + private Object target; + private boolean initialized; + private boolean readOnly; + private boolean unwrap; + private transient SharedSessionContractImplementor session; + private Boolean readOnlyBeforeAttachedToSession; + + private String sessionFactoryUuid; + private boolean allowLoadOutsideTransaction; + + /** + * @deprecated This constructor was initially intended for serialization only, and is not useful anymore. + * In any case it should not be relied on by user code. + * Subclasses should rather implement Serializable with an {@code Object writeReplace()} method returning + * a subclass of {@link AbstractSerializableProxy}, + * which in turn implements Serializable and an {@code Object readResolve()} method + * instantiating the {@link AbstractLazyInitializer} subclass + * and calling {@link AbstractSerializableProxy#afterDeserialization(AbstractLazyInitializer)} on it. + * See {@link org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor} and + * {@link org.hibernate.proxy.pojo.bytebuddy.SerializableProxy} for examples. + */ + @Deprecated + protected AbstractLazyInitializer() { + } + + /** + * Main constructor. + * + * @param entityName The name of the entity being proxied. + * @param id The identifier of the entity being proxied. + * @param session The session owning the proxy. + */ + protected AbstractLazyInitializer(String entityName, Serializable id, SharedSessionContractImplementor session) { + this.entityName = entityName; + this.id = id; + // initialize other fields depending on session state + if ( session == null ) { + unsetSession(); + } + else { + setSession( session ); + } + } + + @Override + public final String getEntityName() { + return entityName; + } + + @Override + public final Serializable getInternalIdentifier() { + return id; + } + + @Override + public final Serializable getIdentifier() { + if ( isUninitialized() && isInitializeProxyWhenAccessingIdentifier() ) { + initialize(); + } + return id; + } + + private boolean isInitializeProxyWhenAccessingIdentifier() { + return getSession() != null && getSession().getFactory() + .getSessionFactoryOptions() + .getJpaCompliance().isJpaProxyComplianceEnabled(); + } + + @Override + public final void setIdentifier(Serializable id) { + this.id = id; + } + + @Override + public final boolean isUninitialized() { + return !initialized; + } + + @Override + public final SharedSessionContractImplementor getSession() { + return session; + } + + @Override + public final void setSession(SharedSessionContractImplementor s) throws HibernateException { + if ( s != session ) { + // check for s == null first, since it is least expensive + if ( s == null ) { + unsetSession(); + } + else if ( isConnectedToSession() ) { + //TODO: perhaps this should be some other RuntimeException... + LOG.attemptToAssociateProxyWithTwoOpenSessions( + entityName, + id + ); + throw new HibernateException( "illegally attempted to associate proxy [" + entityName + "#" + id + "] with two open Sessions" ); + } + else { + // s != null + session = s; + if ( readOnlyBeforeAttachedToSession == null ) { + // use the default read-only/modifiable setting + final EntityPersister persister = s.getFactory().getEntityPersister( entityName ); + setReadOnly( s.getPersistenceContext().isDefaultReadOnly() || !persister.isMutable() ); + } + else { + // use the read-only/modifiable setting indicated during deserialization + setReadOnly( readOnlyBeforeAttachedToSession ); + readOnlyBeforeAttachedToSession = null; + } + } + } + } + + private static EntityKey generateEntityKeyOrNull(Serializable id, SharedSessionContractImplementor s, String entityName) { + if ( id == null || s == null || entityName == null ) { + return null; + } + return s.generateEntityKey( id, s.getFactory().getEntityPersister( entityName ) ); + } + + @Override + public final void unsetSession() { + prepareForPossibleLoadingOutsideTransaction(); + session = null; + readOnly = false; + readOnlyBeforeAttachedToSession = null; + } + + @Override + public final void initialize() throws HibernateException { + if ( !initialized ) { + if ( allowLoadOutsideTransaction ) { + permissiveInitialization(); + } + else if ( session == null ) { + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - no Session" ); + } + else if ( !session.isOpenOrWaitingForAutoClose() ) { + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session was closed" ); + } + else if ( !session.isConnected() ) { + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session is disconnected" ); + } + else { + target = session.immediateLoad( entityName, id ); + initialized = true; + checkTargetState(session); + } + } + else { + checkTargetState(session); + } + } + + protected void permissiveInitialization() { + if ( session == null ) { + //we have a detached collection that is set to null, reattach + if ( sessionFactoryUuid == null ) { + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - no Session" ); + } + try { + SessionFactoryImplementor sf = (SessionFactoryImplementor) + SessionFactoryRegistry.INSTANCE.getSessionFactory( sessionFactoryUuid ); + SharedSessionContractImplementor session = (SharedSessionContractImplementor) sf.openSession(); + session.getPersistenceContext().setDefaultReadOnly( true ); + session.setFlushMode( FlushMode.MANUAL ); + + boolean isJTA = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); + + if ( !isJTA ) { + // Explicitly handle the transactions only if we're not in + // a JTA environment. A lazy loading temporary session can + // be created even if a current session and transaction are + // open (ex: session.clear() was used). We must prevent + // multiple transactions. + session.beginTransaction(); + } + + try { + target = session.immediateLoad( entityName, id ); + initialized = true; + checkTargetState(session); + } + finally { + // make sure the just opened temp session gets closed! + try { + if ( !isJTA ) { + session.getTransaction().commit(); + } + session.close(); + } + catch (Exception e) { + LOG.warn( "Unable to close temporary session used to load lazy proxy associated to no session" ); + } + } + } + catch (Exception e) { + LOG.error( "Initialization failure [" + entityName + "#" + id + "]", e ); + throw new LazyInitializationException( e.getMessage() ); + } + } + else if ( session.isOpenOrWaitingForAutoClose() && session.isConnected() ) { + target = session.immediateLoad( entityName, id ); + initialized = true; + checkTargetState(session); + } + else { + throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - Session was closed or disced" ); + } + } + + /** + * Attempt to initialize the proxy without loading anything from the database. + * + * This will only have any effect if the proxy is still attached to a session, + * and the entity being proxied has been loaded and added to the persistence context + * of that session since the proxy was created. + */ + public final void initializeWithoutLoadIfPossible() { + if ( !initialized && session != null && session.isOpenOrWaitingForAutoClose() ) { + final EntityKey key = session.generateEntityKey( + getInternalIdentifier(), + session.getFactory().getMetamodel().entityPersister( getEntityName() ) + ); + final Object entity = session.getPersistenceContextInternal().getEntity( key ); + if ( entity != null ) { + setImplementation( entity ); + } + } + } + + /** + * Initialize internal state based on the currently attached session, + * in order to be ready to load data even after the proxy is detached from the session. + * + * This method only has any effect if + * {@link SessionFactoryOptions#isInitializeLazyStateOutsideTransactionsEnabled()} is {@code true}. + */ + protected void prepareForPossibleLoadingOutsideTransaction() { + if ( session != null ) { + allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); + + if ( allowLoadOutsideTransaction && sessionFactoryUuid == null ) { + sessionFactoryUuid = session.getFactory().getUuid(); + } + } + } + + private void checkTargetState(SharedSessionContractImplementor session) { + if ( !unwrap ) { + if ( target == null ) { + session.getFactory().getEntityNotFoundDelegate().handleEntityNotFound( entityName, id ); + } + } + } + + /** + * Getter for property 'connectedToSession'. + * + * @return Value for property 'connectedToSession'. + */ + protected final boolean isConnectedToSession() { + return getProxyOrNull() != null; + } + + private Object getProxyOrNull() { + final EntityKey entityKey = generateEntityKeyOrNull( getInternalIdentifier(), session, getEntityName() ); + if ( entityKey != null && session != null && session.isOpenOrWaitingForAutoClose() ) { + return session.getPersistenceContextInternal().getProxy( entityKey ); + } + return null; + } + + @Override + public final Object getImplementation() { + initialize(); + return target; + } + + @Override + public final void setImplementation(Object target) { + this.target = target; + initialized = true; + } + + @Override + public final Object getImplementation(SharedSessionContractImplementor s) throws HibernateException { + final EntityKey entityKey = generateEntityKeyOrNull( getInternalIdentifier(), s, getEntityName() ); + return ( entityKey == null ? null : s.getPersistenceContext().getEntity( entityKey ) ); + } + + /** + * Getter for property 'target'. + *

+ * Same as {@link #getImplementation()} except that this method will not force initialization. + * + * @return Value for property 'target'. + */ + protected final Object getTarget() { + return target; + } + + @Override + public final boolean isReadOnlySettingAvailable() { + return (session != null && !session.isClosed()); + } + + private void errorIfReadOnlySettingNotAvailable() { + if ( session == null ) { + throw new TransientObjectException( + "Proxy [" + entityName + "#" + id + "] is detached (i.e, session is null). The read-only/modifiable setting is only accessible when the proxy is associated with an open session." + ); + } + if ( !session.isOpenOrWaitingForAutoClose() ) { + throw new SessionException( + "Session is closed. The read-only/modifiable setting is only accessible when the proxy [" + entityName + "#" + id + "] is associated with an open session." + ); + } + } + + @Override + public final boolean isReadOnly() { + errorIfReadOnlySettingNotAvailable(); + return readOnly; + } + + @Override + public final void setReadOnly(boolean readOnly) { + errorIfReadOnlySettingNotAvailable(); + // only update if readOnly is different from current setting + if ( this.readOnly != readOnly ) { + final EntityPersister persister = session.getFactory().getEntityPersister( entityName ); + if ( !persister.isMutable() && !readOnly ) { + throw new IllegalStateException( "cannot make proxies [" + entityName + "#" + id + "] for immutable entities modifiable" ); + } + this.readOnly = readOnly; + if ( initialized ) { + EntityKey key = generateEntityKeyOrNull( getInternalIdentifier(), session, getEntityName() ); + final PersistenceContext persistenceContext = session.getPersistenceContext(); + if ( key != null && persistenceContext.containsEntity( key ) ) { + persistenceContext.setReadOnly( target, readOnly ); + } + } + } + } + + /** + * Get the read-only/modifiable setting that should be put in affect when it is + * attached to a session. + *

+ * This method should only be called during serialization when read-only/modifiable setting + * is not available (i.e., isReadOnlySettingAvailable() == false) + * + * @return null, if the default setting should be used; + * true, for read-only; + * false, for modifiable + * + * @throws IllegalStateException if isReadOnlySettingAvailable() == true + */ + public final Boolean isReadOnlyBeforeAttachedToSession() { + if ( isReadOnlySettingAvailable() ) { + throw new IllegalStateException( + "Cannot call isReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" + ); + } + return readOnlyBeforeAttachedToSession; + } + + /** + * Get whether the proxy can load data even + * if it's not attached to a session with an ongoing transaction. + * + * This method should only be called during serialization, + * and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}. + * + * @return {@code true} if out-of-transaction loads are allowed, {@code false} otherwise. + */ + protected boolean isAllowLoadOutsideTransaction() { + return allowLoadOutsideTransaction; + } + + /** + * Get the session factory UUID. + * + * This method should only be called during serialization, + * and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}. + * + * @return the session factory UUID. + */ + protected String getSessionFactoryUuid() { + return sessionFactoryUuid; + } + + /** + * Restore settings that are not passed to the constructor, + * but are still preserved during serialization. + * + * This method should only be called during deserialization, before associating + * the proxy with a session. + * + * @param readOnlyBeforeAttachedToSession the read-only/modifiable setting to use when + * associated with a session; null indicates that the default should be used. + * @param sessionFactoryUuid the session factory uuid, to be used if {@code allowLoadOutsideTransaction} is {@code true}. + * @param allowLoadOutsideTransaction whether the proxy can load data even + * if it's not attached to a session with an ongoing transaction. + * + * @throws IllegalStateException if isReadOnlySettingAvailable() == true + */ + /* package-private */ + final void afterDeserialization(Boolean readOnlyBeforeAttachedToSession, + String sessionFactoryUuid, boolean allowLoadOutsideTransaction) { + if ( isReadOnlySettingAvailable() ) { + throw new IllegalStateException( + "Cannot call afterDeserialization when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" + ); + } + this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession; + + this.sessionFactoryUuid = sessionFactoryUuid; + this.allowLoadOutsideTransaction = allowLoadOutsideTransaction; + } + + @Override + public boolean isUnwrap() { + return unwrap; + } + + @Override + public void setUnwrap(boolean unwrap) { + this.unwrap = unwrap; + } +}