org.hibernate.collection.internal.AbstractPersistentCollection Maven / Gradle / Ivy
                 Go to download
                
        
                    Show more of this group  Show more artifacts with this name
Show all versions of hibernate-core Show documentation
                Show all versions of hibernate-core Show documentation
The core O/RM functionality as provided by Hibernate
                
             The newest version!
        
        /*
 * 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.collection.internal;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import javax.naming.NamingException;
import org.hibernate.AssertionFailure;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.LazyInitializationException;
import org.hibernate.Session;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.internal.ForeignKeys;
import org.hibernate.engine.spi.CollectionEntry;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.SessionFactoryRegistry;
import org.hibernate.internal.util.MarkerObject;
import org.hibernate.internal.util.collections.EmptyIterator;
import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.type.ComponentType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.IntegerType;
import org.hibernate.type.LongType;
import org.hibernate.type.PostgresUUIDType;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;
import org.hibernate.type.UUIDBinaryType;
import org.hibernate.type.UUIDCharType;
/**
 * Base class implementing {@link org.hibernate.collection.spi.PersistentCollection}
 *
 * @author Gavin King
 */
public abstract class AbstractPersistentCollection implements Serializable, PersistentCollection {
	private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractPersistentCollection.class );
	private transient SessionImplementor session;
	private boolean isTempSession = false;
	private boolean initialized;
	private transient List operationQueue;
	private transient boolean directlyAccessible;
	private transient boolean initializing;
	private Object owner;
	private int cachedSize = -1;
	private String role;
	private Serializable key;
	// collections detect changes made via their public interface and mark
	// themselves as dirty as a performance optimization
	private boolean dirty;
	private Serializable storedSnapshot;
	private String sessionFactoryUuid;
	private boolean allowLoadOutsideTransaction;
	/**
	 * Not called by Hibernate, but used by non-JDK serialization,
	 * eg. SOAP libraries.
	 */
	public AbstractPersistentCollection() {
	}
	protected AbstractPersistentCollection(SessionImplementor session) {
		this.session = session;
	}
	@Override
	public final String getRole() {
		return role;
	}
	@Override
	public final Serializable getKey() {
		return key;
	}
	@Override
	public final boolean isUnreferenced() {
		return role == null;
	}
	@Override
	public final boolean isDirty() {
		return dirty;
	}
	@Override
	public final void clearDirty() {
		dirty = false;
	}
	@Override
	public final void dirty() {
		dirty = true;
	}
	@Override
	public final Serializable getStoredSnapshot() {
		return storedSnapshot;
	}
	//Careful: these methods do not initialize the collection.
	@Override
	public abstract boolean empty();
	/**
	 * Called by any read-only method of the collection interface
	 */
	protected final void read() {
		initialize( false );
	}
	/**
	 * Called by the {@link Collection#size} method
	 */
	@SuppressWarnings({"JavaDoc"})
	protected boolean readSize() {
		if ( !initialized ) {
			if ( cachedSize != -1 && !hasQueuedOperations() ) {
				return true;
			}
			else {
				final boolean isExtraLazy = withTemporarySessionIfNeeded(
						new LazyInitializationWork() {
							@Override
							public Boolean doWork() {
								final CollectionEntry entry = session.getPersistenceContext().getCollectionEntry( AbstractPersistentCollection.this );
								if ( entry != null ) {
									final CollectionPersister persister = entry.getLoadedPersister();
									if ( persister.isExtraLazy() ) {
										if ( hasQueuedOperations() ) {
											session.flush();
										}
										cachedSize = persister.getSize( entry.getLoadedKey(), session );
										return true;
									}
									else {
										read();
									}
								}
								else{
									throwLazyInitializationExceptionIfNotConnected();
								}
								return false;
							}
						}
				);
				if ( isExtraLazy ) {
					return true;
				}
			}
		}
		return false;
	}
	/**
	 * TBH not sure why this is public
	 *
	 * @param  The java type of the return for this LazyInitializationWork
	 */
	public static interface LazyInitializationWork {
		/**
		 * Do the represented work and return the result.
		 *
		 * @return The result
		 */
		public T doWork();
	}
	private  T withTemporarySessionIfNeeded(LazyInitializationWork lazyInitializationWork) {
		SessionImplementor tempSession = null;
		if ( session == null ) {
			if ( allowLoadOutsideTransaction ) {
				tempSession = openTemporarySessionForLoading();
			}
			else {
				throwLazyInitializationException( "could not initialize proxy - no Session" );
			}
		}
		else if ( !session.isOpen() ) {
			if ( allowLoadOutsideTransaction ) {
				tempSession = openTemporarySessionForLoading();
			}
			else {
				throwLazyInitializationException( "could not initialize proxy - the owning Session was closed" );
			}
		}
		else if ( !session.isConnected() ) {
			if ( allowLoadOutsideTransaction ) {
				tempSession = openTemporarySessionForLoading();
			}
			else {
				throwLazyInitializationException( "could not initialize proxy - the owning Session is disconnected" );
			}
		}
		SessionImplementor originalSession = null;
		boolean isJTA = false;
		if ( tempSession != null ) {
			isTempSession = true;
			originalSession = session;
			session = tempSession;
			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();
			}
			session.getPersistenceContext().addUninitializedDetachedCollection(
					session.getFactory().getCollectionPersister( getRole() ),
					this
			);
		}
		try {
			return lazyInitializationWork.doWork();
		}
		finally {
			if ( tempSession != null ) {
				// make sure the just opened temp session gets closed!
				isTempSession = false;
				session = originalSession;
				try {
					if ( !isJTA ) {
						( (Session) tempSession ).getTransaction().commit();
					}
					( (Session) tempSession ).close();
				}
				catch (Exception e) {
					LOG.warn( "Unable to close temporary session used to load lazy collection associated to no session" );
				}
			}
		}
	}
	private SessionImplementor openTemporarySessionForLoading() {
		if ( sessionFactoryUuid == null ) {
			throwLazyInitializationException( "SessionFactory UUID not known to create temporary Session for loading" );
		}
		final SessionFactoryImplementor sf = (SessionFactoryImplementor)
				SessionFactoryRegistry.INSTANCE.getSessionFactory( sessionFactoryUuid );
		final SessionImplementor session = (SessionImplementor) sf.openSession();
		session.getPersistenceContext().setDefaultReadOnly( true );
		session.setFlushMode( FlushMode.MANUAL );
		return session;
	}
	protected Boolean readIndexExistence(final Object index) {
		if ( !initialized ) {
			final Boolean extraLazyExistenceCheck = withTemporarySessionIfNeeded(
					new LazyInitializationWork() {
						@Override
						public Boolean doWork() {
							final CollectionEntry entry = session.getPersistenceContext().getCollectionEntry( AbstractPersistentCollection.this );
							final CollectionPersister persister = entry.getLoadedPersister();
							if ( persister.isExtraLazy() ) {
								if ( hasQueuedOperations() ) {
									session.flush();
								}
								return persister.indexExists( entry.getLoadedKey(), index, session );
							}
							else {
								read();
							}
							return null;
						}
					}
			);
			if ( extraLazyExistenceCheck != null ) {
				return extraLazyExistenceCheck;
			}
		}
		return null;
	}
	protected Boolean readElementExistence(final Object element) {
		if ( !initialized ) {
			final Boolean extraLazyExistenceCheck = withTemporarySessionIfNeeded(
					new LazyInitializationWork() {
						@Override
						public Boolean doWork() {
							final CollectionEntry entry = session.getPersistenceContext().getCollectionEntry( AbstractPersistentCollection.this );
							final CollectionPersister persister = entry.getLoadedPersister();
							if ( persister.isExtraLazy() ) {
								if ( hasQueuedOperations() ) {
									session.flush();
								}
								return persister.elementExists( entry.getLoadedKey(), element, session );
							}
							else {
								read();
							}
							return null;
						}
					}
			);
			if ( extraLazyExistenceCheck != null ) {
				return extraLazyExistenceCheck;
			}
		}
		return null;
	}
	protected static final Object UNKNOWN = new MarkerObject( "UNKNOWN" );
	protected Object readElementByIndex(final Object index) {
		if ( !initialized ) {
			class ExtraLazyElementByIndexReader implements LazyInitializationWork {
				private boolean isExtraLazy;
				private Object element;
				@Override
				public Object doWork() {
					final CollectionEntry entry = session.getPersistenceContext().getCollectionEntry( AbstractPersistentCollection.this );
					final CollectionPersister persister = entry.getLoadedPersister();
					isExtraLazy = persister.isExtraLazy();
					if ( isExtraLazy ) {
						if ( hasQueuedOperations() ) {
							session.flush();
						}
						element = persister.getElementByIndex( entry.getLoadedKey(), index, session, owner );
					}
					else {
						read();
					}
					return null;
				}
			}
			final ExtraLazyElementByIndexReader reader = new ExtraLazyElementByIndexReader();
			//noinspection unchecked
			withTemporarySessionIfNeeded( reader );
			if ( reader.isExtraLazy ) {
				return reader.element;
			}
		}
		return UNKNOWN;
	}
	protected int getCachedSize() {
		return cachedSize;
	}
	protected boolean isConnectedToSession() {
		return session != null
				&& session.isOpen()
				&& session.getPersistenceContext().containsCollection( this );
	}
	protected boolean isInitialized() {
		return initialized;
	}
	/**
	 * Called by any writer method of the collection interface
	 */
	protected final void write() {
		initialize( true );
		dirty();
	}
	/**
	 * Is this collection in a state that would allow us to
	 * "queue" operations?
	 */
	@SuppressWarnings({"JavaDoc"})
	protected boolean isOperationQueueEnabled() {
		return !initialized
				&& isConnectedToSession()
				&& isInverseCollection();
	}
	/**
	 * Is this collection in a state that would allow us to
	 * "queue" puts? This is a special case, because of orphan
	 * delete.
	 */
	@SuppressWarnings({"JavaDoc"})
	protected boolean isPutQueueEnabled() {
		return !initialized
				&& isConnectedToSession()
				&& isInverseOneToManyOrNoOrphanDelete();
	}
	/**
	 * Is this collection in a state that would allow us to
	 * "queue" clear? This is a special case, because of orphan
	 * delete.
	 */
	@SuppressWarnings({"JavaDoc"})
	protected boolean isClearQueueEnabled() {
		return !initialized
				&& isConnectedToSession()
				&& isInverseCollectionNoOrphanDelete();
	}
	/**
	 * Is this the "inverse" end of a bidirectional association?
	 */
	@SuppressWarnings({"JavaDoc"})
	protected boolean isInverseCollection() {
		final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( this );
		return ce != null && ce.getLoadedPersister().isInverse();
	}
	/**
	 * Is this the "inverse" end of a bidirectional association with
	 * no orphan delete enabled?
	 */
	@SuppressWarnings({"JavaDoc"})
	protected boolean isInverseCollectionNoOrphanDelete() {
		final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( this );
		return ce != null
				&&
				ce.getLoadedPersister().isInverse() &&
				!ce.getLoadedPersister().hasOrphanDelete();
	}
	/**
	 * Is this the "inverse" end of a bidirectional one-to-many, or
	 * of a collection with no orphan delete?
	 */
	@SuppressWarnings({"JavaDoc"})
	protected boolean isInverseOneToManyOrNoOrphanDelete() {
		final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( this );
		return ce != null
				&& ce.getLoadedPersister().isInverse()
				&& ( ce.getLoadedPersister().isOneToMany() || !ce.getLoadedPersister().hasOrphanDelete() );
	}
	/**
	 * Queue an addition
	 */
	@SuppressWarnings({"JavaDoc"})
	protected final void queueOperation(DelayedOperation operation) {
		if ( operationQueue == null ) {
			operationQueue = new ArrayList( 10 );
		}
		operationQueue.add( operation );
		//needed so that we remove this collection from the second-level cache
		dirty = true;
	}
	/**
	 * Replace entity instances with copy in {@code copyCache}/.
	 *
	 * @param copyCache - mapping from entity in the process of being
	 *                    merged to managed copy.
	 */
	public final void replaceQueuedOperationValues(CollectionPersister persister, Map copyCache) {
		for ( DelayedOperation operation : operationQueue ) {
			if ( ValueDelayedOperation.class.isInstance( operation ) ) {
				( (ValueDelayedOperation) operation ).replace( persister, copyCache );
			}
		}
	}
	/**
	 * After reading all existing elements from the database,
	 * add the queued elements to the underlying collection.
	 */
	protected final void performQueuedOperations() {
		for ( DelayedOperation operation : operationQueue ) {
			operation.operate();
		}
	}
	@Override
	public void setSnapshot(Serializable key, String role, Serializable snapshot) {
		this.key = key;
		this.role = role;
		this.storedSnapshot = snapshot;
	}
	@Override
	public void postAction() {
		operationQueue = null;
		cachedSize = -1;
		clearDirty();
	}
	@Override
	public Object getValue() {
		return this;
	}
	@Override
	public void beginRead() {
		// override on some subclasses
		initializing = true;
	}
	@Override
	public boolean endRead() {
		//override on some subclasses
		return afterInitialize();
	}
	@Override
	public boolean afterInitialize() {
		setInitialized();
		//do this bit after setting initialized to true or it will recurse
		if ( operationQueue != null ) {
			performQueuedOperations();
			operationQueue = null;
			cachedSize = -1;
			return false;
		}
		else {
			return true;
		}
	}
	/**
	 * Initialize the collection, if possible, wrapping any exceptions
	 * in a runtime exception
	 *
	 * @param writing currently obsolete
	 *
	 * @throws LazyInitializationException if we cannot initialize
	 */
	protected final void initialize(final boolean writing) {
		if ( initialized ) {
			return;
		}
		withTemporarySessionIfNeeded(
				new LazyInitializationWork              © 2015 - 2025 Weber Informatics LLC | Privacy Policy