/*
 * 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.engine.spi;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;

import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.collection.internal.AbstractPersistentCollection;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.internal.CoreLogging;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.pretty.MessageHelper;

import org.jboss.logging.Logger;

We need an entry to tell us all about the current state of a collection with respect to its persistent state
Author:Gavin King
/** * We need an entry to tell us all about the current state * of a collection with respect to its persistent state * * @author Gavin King */
public final class CollectionEntry implements Serializable { private static final Logger LOG = CoreLogging.logger( CollectionEntry.class ); //ATTRIBUTES MAINTAINED BETWEEN FLUSH CYCLES // session-start/post-flush persistent state private Serializable snapshot; // allow the CollectionSnapshot to be serialized private String role; // "loaded" means the reference that is consistent // with the current database state private transient CollectionPersister loadedPersister; private Serializable loadedKey; // ATTRIBUTES USED ONLY DURING FLUSH CYCLE // during flush, we navigate the object graph to // collections and decide what to do with them private transient boolean reached; private transient boolean processed; private transient boolean doupdate; private transient boolean doremove; private transient boolean dorecreate; // if we instantiate a collection during the flush() process, // we must ignore it for the rest of the flush() private transient boolean ignore; // "current" means the reference that was found during flush() private transient CollectionPersister currentPersister; private transient Serializable currentKey;
For newly wrapped collections, or dereferenced collection wrappers
/** * For newly wrapped collections, or dereferenced collection wrappers */
public CollectionEntry(CollectionPersister persister, PersistentCollection collection) { // new collections that get found + wrapped // during flush shouldn't be ignored ignore = false; collection.clearDirty(); //a newly wrapped collection is NOT dirty (or we get unnecessary version updates) snapshot = persister.isMutable() ? collection.getSnapshot(persister) : null; collection.setSnapshot(loadedKey, role, snapshot); }
For collections just loaded from the database
/** * For collections just loaded from the database */
public CollectionEntry( final PersistentCollection collection, final CollectionPersister loadedPersister, final Serializable loadedKey, final boolean ignore ) { this.ignore=ignore; //collection.clearDirty() this.loadedKey = loadedKey; setLoadedPersister(loadedPersister); collection.setSnapshot(loadedKey, role, null); //postInitialize() will be called after initialization }
For uninitialized detached collections
/** * For uninitialized detached collections */
public CollectionEntry(CollectionPersister loadedPersister, Serializable loadedKey) { // detached collection wrappers that get found + reattached // during flush shouldn't be ignored ignore = false; //collection.clearDirty() this.loadedKey = loadedKey; setLoadedPersister(loadedPersister); }
For initialized detached collections
/** * For initialized detached collections */
public CollectionEntry(PersistentCollection collection, SessionFactoryImplementor factory) throws MappingException { // detached collections that get found + reattached // during flush shouldn't be ignored ignore = false; loadedKey = collection.getKey(); setLoadedPersister( factory.getCollectionPersister( collection.getRole() ) ); snapshot = collection.getStoredSnapshot(); }
Used from custom serialization.
See Also:
/** * Used from custom serialization. * * @see #serialize * @see #deserialize */
private CollectionEntry( String role, Serializable snapshot, Serializable loadedKey, SessionFactoryImplementor factory) { this.role = role; this.snapshot = snapshot; this.loadedKey = loadedKey; if ( role != null ) { afterDeserialize( factory ); } }
Determine if the collection is "really" dirty, by checking dirtiness of the collection elements, if necessary
/** * Determine if the collection is "really" dirty, by checking dirtiness * of the collection elements, if necessary */
private void dirty(PersistentCollection collection) throws HibernateException { boolean forceDirty = collection.wasInitialized() && !collection.isDirty() && //optimization getLoadedPersister() != null && getLoadedPersister().isMutable() && //optimization ( collection.isDirectlyAccessible() || getLoadedPersister().getElementType().isMutable() ) && //optimization !collection.equalsSnapshot( getLoadedPersister() ); if ( forceDirty ) { collection.dirty(); } } public void preFlush(PersistentCollection collection) throws HibernateException { if ( loadedKey == null && collection.getKey() != null ) { loadedKey = collection.getKey(); } boolean nonMutableChange = collection.isDirty() && getLoadedPersister()!=null && !getLoadedPersister().isMutable(); if (nonMutableChange) { throw new HibernateException( "changed an immutable collection instance: " + MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() ) ); } dirty(collection); if ( LOG.isDebugEnabled() && collection.isDirty() && getLoadedPersister() != null ) { LOG.debugf( "Collection dirty: %s", MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() ) ); } setDoupdate(false); setDoremove(false); setDorecreate(false); setReached(false); setProcessed(false); } public void postInitialize(PersistentCollection collection) throws HibernateException { snapshot = getLoadedPersister().isMutable() ? collection.getSnapshot( getLoadedPersister() ) : null; collection.setSnapshot(loadedKey, role, snapshot); if (getLoadedPersister().getBatchSize() > 1) { ((AbstractPersistentCollection) collection).getSession().getPersistenceContext().getBatchFetchQueue().removeBatchLoadableCollection(this); } }
Called after a successful flush
/** * Called after a successful flush */
public void postFlush(PersistentCollection collection) throws HibernateException { if ( isIgnore() ) { ignore = false; } else if ( !isProcessed() ) { throw new AssertionFailure( "collection [" + collection.getRole() + "] was not processed by flush()" ); } collection.setSnapshot(loadedKey, role, snapshot); }
Called after execution of an action
/** * Called after execution of an action */
public void afterAction(PersistentCollection collection) { loadedKey = getCurrentKey(); setLoadedPersister( getCurrentPersister() ); boolean resnapshot = collection.wasInitialized() && ( isDoremove() || isDorecreate() || isDoupdate() ); if ( resnapshot ) { snapshot = loadedPersister==null || !loadedPersister.isMutable() ? null : collection.getSnapshot(loadedPersister); //re-snapshot } collection.postAction(); } public Serializable getKey() { return getLoadedKey(); } public String getRole() { return role; } public Serializable getSnapshot() { return snapshot; } private boolean fromMerge;
Reset the stored snapshot for both the persistent collection and this collection entry. Used during the merge of detached collections.
Params:
  • collection – the persistentcollection to be updated
  • storedSnapshot – the new stored snapshot
/** * Reset the stored snapshot for both the persistent collection and this collection entry. * Used during the merge of detached collections. * * @param collection the persistentcollection to be updated * @param storedSnapshot the new stored snapshot */
public void resetStoredSnapshot(PersistentCollection collection, Serializable storedSnapshot) { LOG.debugf("Reset storedSnapshot to %s for %s", storedSnapshot, this); if ( fromMerge ) { return; // EARLY EXIT! } snapshot = storedSnapshot; collection.setSnapshot( loadedKey, role, snapshot ); fromMerge = true; } private void setLoadedPersister(CollectionPersister persister) { loadedPersister = persister; setRole( persister == null ? null : persister.getRole() ); } void afterDeserialize(SessionFactoryImplementor factory) { loadedPersister = ( factory == null ? null : factory.getCollectionPersister(role) ); } public boolean wasDereferenced() { return getLoadedKey() == null; } public boolean isReached() { return reached; } public void setReached(boolean reached) { this.reached = reached; } public boolean isProcessed() { return processed; } public void setProcessed(boolean processed) { this.processed = processed; } public boolean isDoupdate() { return doupdate; } public void setDoupdate(boolean doupdate) { this.doupdate = doupdate; } public boolean isDoremove() { return doremove; } public void setDoremove(boolean doremove) { this.doremove = doremove; } public boolean isDorecreate() { return dorecreate; } public void setDorecreate(boolean dorecreate) { this.dorecreate = dorecreate; } public boolean isIgnore() { return ignore; } public CollectionPersister getCurrentPersister() { return currentPersister; } public void setCurrentPersister(CollectionPersister currentPersister) { this.currentPersister = currentPersister; }
This is only available late during the flush cycle
/** * This is only available late during the flush * cycle */
public Serializable getCurrentKey() { return currentKey; } public void setCurrentKey(Serializable currentKey) { this.currentKey = currentKey; }
This is only available late during the flush cycle
/** * This is only available late during the flush cycle */
public CollectionPersister getLoadedPersister() { return loadedPersister; } public Serializable getLoadedKey() { return loadedKey; } public void setRole(String role) { this.role = role; } @Override public String toString() { String result = "CollectionEntry" + MessageHelper.collectionInfoString( loadedPersister.getRole(), loadedKey ); if ( currentPersister != null ) { result += "->" + MessageHelper.collectionInfoString( currentPersister.getRole(), currentKey ); } return result; }
Get the collection orphans (entities which were removed from the collection)
/** * Get the collection orphans (entities which were removed from the collection) */
public Collection getOrphans(String entityName, PersistentCollection collection) throws HibernateException { if (snapshot==null) { throw new AssertionFailure("no collection snapshot for orphan delete"); } return collection.getOrphans( snapshot, entityName ); } public boolean isSnapshotEmpty(PersistentCollection collection) { //TODO: does this really need to be here? // does the collection already have // it's own up-to-date snapshot? return collection.wasInitialized() && ( getLoadedPersister()==null || getLoadedPersister().isMutable() ) && collection.isSnapshotEmpty( getSnapshot() ); }
Custom serialization routine used during serialization of a Session/PersistenceContext for increased performance.
Params:
  • oos – The stream to which we should write the serial data.
Throws:
/** * Custom serialization routine used during serialization of a * Session/PersistenceContext for increased performance. * * @param oos The stream to which we should write the serial data. * @throws java.io.IOException */
public void serialize(ObjectOutputStream oos) throws IOException { oos.writeObject( role ); oos.writeObject( snapshot ); oos.writeObject( loadedKey ); }
Custom deserialization routine used during deserialization of a Session/PersistenceContext for increased performance.
Params:
  • ois – The stream from which to read the entry.
  • session – The session being deserialized.
Throws:
Returns:The deserialized CollectionEntry
/** * Custom deserialization routine used during deserialization of a * Session/PersistenceContext for increased performance. * * @param ois The stream from which to read the entry. * @param session The session being deserialized. * * @return The deserialized CollectionEntry * * @throws IOException * @throws ClassNotFoundException */
public static CollectionEntry deserialize( ObjectInputStream ois, SessionImplementor session) throws IOException, ClassNotFoundException { return new CollectionEntry( (String) ois.readObject(), (Serializable) ois.readObject(), (Serializable) ois.readObject(), (session == null ? null : session.getFactory()) ); } }