/*
* 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.loading.internal;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CollectionKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.IdentityMap;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.pretty.MessageHelper;
/**
* Maps {@link ResultSet result-sets} to specific contextual data
* related to processing that {@link ResultSet result-sets}.
* <p/>
* Implementation note: internally an {@link IdentityMap} is used to maintain
* the mappings; {@link IdentityMap} was chosen because I'd rather not be
* dependent upon potentially bad {@link ResultSet#equals} and {ResultSet#hashCode}
* implementations.
* <p/>
* Considering the JDBC-redesign work, would further like this contextual info
* not mapped seperately, but available based on the result set being processed.
* This would also allow maintaining a single mapping as we could reliably get
* notification of the result-set closing...
*
* @author Steve Ebersole
*/
public class LoadContexts {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, LoadContexts.class.getName());
private final PersistenceContext persistenceContext;
private Map<ResultSet,CollectionLoadContext> collectionLoadContexts;
private Map<ResultSet,EntityLoadContext> entityLoadContexts;
private Map<CollectionKey,LoadingCollectionEntry> xrefLoadingCollectionEntries;
/**
* Creates and binds this to the given persistence context.
*
* @param persistenceContext The persistence context to which this
* will be bound.
*/
public LoadContexts(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
}
/**
* Retrieves the persistence context to which this is bound.
*
* @return The persistence context to which this is bound.
*/
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
private SessionImplementor getSession() {
return getPersistenceContext().getSession();
}
// cleanup code ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Release internal state associated with the given result set.
* <p/>
* This should be called when we are done with processing said result set,
* ideally as the result set is being closed.
*
* @param resultSet The result set for which it is ok to release
* associated resources.
*/
public void cleanup(ResultSet resultSet) {
if ( collectionLoadContexts != null ) {
CollectionLoadContext collectionLoadContext = collectionLoadContexts.remove( resultSet );
collectionLoadContext.cleanup();
}
if ( entityLoadContexts != null ) {
EntityLoadContext entityLoadContext = entityLoadContexts.remove( resultSet );
entityLoadContext.cleanup();
}
}
/**
* Release internal state associated with *all* result sets.
* <p/>
* This is intended as a "failsafe" process to make sure we get everything
* cleaned up and released.
*/
public void cleanup() {
if ( collectionLoadContexts != null ) {
for ( CollectionLoadContext collectionLoadContext : collectionLoadContexts.values() ) {
LOG.failSafeCollectionsCleanup( collectionLoadContext );
collectionLoadContext.cleanup();
}
collectionLoadContexts.clear();
}
if ( entityLoadContexts != null ) {
for ( EntityLoadContext entityLoadContext : entityLoadContexts.values() ) {
LOG.failSafeEntitiesCleanup( entityLoadContext );
entityLoadContext.cleanup();
}
entityLoadContexts.clear();
}
}
// Collection load contexts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Do we currently have any internal entries corresponding to loading
* collections?
*
* @return True if we currently hold state pertaining to loading collections;
* false otherwise.
*/
public boolean hasLoadingCollectionEntries() {
return ( collectionLoadContexts != null && !collectionLoadContexts.isEmpty() );
}
/**
* Do we currently have any registered internal entries corresponding to loading
* collections?
*
* @return True if we currently hold state pertaining to a registered loading collections;
* false otherwise.
*/
public boolean hasRegisteredLoadingCollectionEntries() {
return ( xrefLoadingCollectionEntries != null && !xrefLoadingCollectionEntries.isEmpty() );
}
/**
* Get the {@link CollectionLoadContext} associated with the given
* {@link ResultSet}, creating one if needed.
*
* @param resultSet The result set for which to retrieve the context.
* @return The processing context.
*/
public CollectionLoadContext getCollectionLoadContext(ResultSet resultSet) {
CollectionLoadContext context = null;
if ( collectionLoadContexts == null ) {
collectionLoadContexts = IdentityMap.instantiate( 8 );
}
else {
context = collectionLoadContexts.get(resultSet);
}
if ( context == null ) {
if (LOG.isTraceEnabled()) {
LOG.trace("Constructing collection load context for result set [" + resultSet + "]");
}
context = new CollectionLoadContext( this, resultSet );
collectionLoadContexts.put( resultSet, context );
}
return context;
}
/**
* Attempt to locate the loading collection given the owner's key. The lookup here
* occurs against all result-set contexts...
*
* @param persister The collection persister
* @param ownerKey The owner key
* @return The loading collection, or null if not found.
*/
public PersistentCollection locateLoadingCollection(CollectionPersister persister, Serializable ownerKey) {
LoadingCollectionEntry lce = locateLoadingCollectionEntry( new CollectionKey( persister, ownerKey ) );
if ( lce != null ) {
if ( LOG.isTraceEnabled() ) {
LOG.tracef(
"Returning loading collection: %s",
MessageHelper.collectionInfoString( persister, ownerKey, getSession().getFactory() )
);
}
return lce.getCollection();
}
// TODO : should really move this log statement to CollectionType, where this is used from...
if ( LOG.isTraceEnabled() ) {
LOG.tracef(
"Creating collection wrapper: %s",
MessageHelper.collectionInfoString( persister, ownerKey, getSession().getFactory() )
);
}
return null;
}
// loading collection xrefs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Register a loading collection xref.
* <p/>
* This xref map is used because sometimes a collection is in process of
* being loaded from one result set, but needs to be accessed from the
* context of another "nested" result set processing.
* <p/>
* Implementation note: package protected, as this is meant solely for use
* by {@link CollectionLoadContext} to be able to locate collections
* being loaded by other {@link CollectionLoadContext}s/{@link ResultSet}s.
*
* @param entryKey The xref collection key
* @param entry The corresponding loading collection entry
*/
void registerLoadingCollectionXRef(CollectionKey entryKey, LoadingCollectionEntry entry) {
if ( xrefLoadingCollectionEntries == null ) {
xrefLoadingCollectionEntries = new HashMap<CollectionKey,LoadingCollectionEntry>();
}
xrefLoadingCollectionEntries.put( entryKey, entry );
}
/**
* The inverse of {@link #registerLoadingCollectionXRef}. Here, we are done
* processing the said collection entry, so we remove it from the
* load context.
* <p/>
* The idea here is that other loading collections can now reference said
* collection directly from the {@link PersistenceContext} because it
* has completed its load cycle.
* <p/>
* Implementation note: package protected, as this is meant solely for use
* by {@link CollectionLoadContext} to be able to locate collections
* being loaded by other {@link CollectionLoadContext}s/{@link ResultSet}s.
*
* @param key The key of the collection we are done processing.
*/
void unregisterLoadingCollectionXRef(CollectionKey key) {
if ( !hasRegisteredLoadingCollectionEntries() ) {
return;
}
xrefLoadingCollectionEntries.remove(key);
}
/*package*/Map getLoadingCollectionXRefs() {
return xrefLoadingCollectionEntries;
}
/**
* Locate the LoadingCollectionEntry within *any* of the tracked
* {@link CollectionLoadContext}s.
* <p/>
* Implementation note: package protected, as this is meant solely for use
* by {@link CollectionLoadContext} to be able to locate collections
* being loaded by other {@link CollectionLoadContext}s/{@link ResultSet}s.
*
* @param key The collection key.
* @return The located entry; or null.
*/
LoadingCollectionEntry locateLoadingCollectionEntry(CollectionKey key) {
if (xrefLoadingCollectionEntries == null) return null;
LOG.trace("Attempting to locate loading collection entry [" + key + "] in any result-set context");
LoadingCollectionEntry rtn = xrefLoadingCollectionEntries.get( key );
if (rtn == null) LOG.trace("Collection [" + key + "] not located in load context");
else LOG.trace("Collection [" + key + "] located in load context");
return rtn;
}
/*package*/void cleanupCollectionXRefs(Set<CollectionKey> entryKeys) {
for ( CollectionKey entryKey : entryKeys ) {
xrefLoadingCollectionEntries.remove( entryKey );
}
}
// Entity load contexts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// * currently, not yet used...
public EntityLoadContext getEntityLoadContext(ResultSet resultSet) {
EntityLoadContext context = null;
if ( entityLoadContexts == null ) {
entityLoadContexts = IdentityMap.instantiate( 8 );
}
else {
context = entityLoadContexts.get( resultSet );
}
if ( context == null ) {
context = new EntityLoadContext( this, resultSet );
entityLoadContexts.put( resultSet, context );
}
return context;
}
}