package org.hibernate.loader.plan.build.internal;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.loader.PropertyPath;
import org.hibernate.loader.plan.build.internal.returns.CollectionReturnImpl;
import org.hibernate.loader.plan.build.internal.returns.EntityReturnImpl;
import org.hibernate.loader.plan.build.internal.spaces.QuerySpacesImpl;
import org.hibernate.loader.plan.build.spi.ExpandingEntityIdentifierDescription;
import org.hibernate.loader.plan.build.spi.ExpandingFetchSource;
import org.hibernate.loader.plan.build.spi.ExpandingQuerySpaces;
import org.hibernate.loader.plan.build.spi.LoadPlanBuildingAssociationVisitationStrategy;
import org.hibernate.loader.plan.build.spi.LoadPlanBuildingContext;
import org.hibernate.loader.plan.spi.AttributeFetch;
import org.hibernate.loader.plan.spi.CollectionAttributeFetch;
import org.hibernate.loader.plan.spi.CollectionFetchableElement;
import org.hibernate.loader.plan.spi.CollectionFetchableIndex;
import org.hibernate.loader.plan.spi.CollectionReference;
import org.hibernate.loader.plan.spi.CollectionReturn;
import org.hibernate.loader.plan.spi.CompositeAttributeFetch;
import org.hibernate.loader.plan.spi.CompositeFetch;
import org.hibernate.loader.plan.spi.EntityFetch;
import org.hibernate.loader.plan.spi.EntityReference;
import org.hibernate.loader.plan.spi.EntityReturn;
import org.hibernate.loader.plan.spi.FetchSource;
import org.hibernate.loader.plan.spi.Return;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.walking.internal.FetchStrategyHelper;
import org.hibernate.persister.walking.spi.AnyMappingDefinition;
import org.hibernate.persister.walking.spi.AssociationAttributeDefinition;
import org.hibernate.persister.walking.spi.AssociationKey;
import org.hibernate.persister.walking.spi.AttributeDefinition;
import org.hibernate.persister.walking.spi.CollectionDefinition;
import org.hibernate.persister.walking.spi.CollectionElementDefinition;
import org.hibernate.persister.walking.spi.CollectionIndexDefinition;
import org.hibernate.persister.walking.spi.CompositionDefinition;
import org.hibernate.persister.walking.spi.EntityDefinition;
import org.hibernate.persister.walking.spi.EntityIdentifierDefinition;
import org.hibernate.persister.walking.spi.WalkingException;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;
import org.jboss.logging.MDC;
public abstract class AbstractLoadPlanBuildingAssociationVisitationStrategy
implements LoadPlanBuildingAssociationVisitationStrategy, LoadPlanBuildingContext {
private static final Logger log = Logger.getLogger( AbstractLoadPlanBuildingAssociationVisitationStrategy.class );
private static final String MDC_KEY = "hibernateLoadPlanWalkPath";
private final SessionFactoryImplementor sessionFactory;
private final QuerySpacesImpl querySpaces;
private final PropertyPathStack propertyPathStack = new PropertyPathStack();
private final ArrayDeque<ExpandingFetchSource> fetchSourceStack = new ArrayDeque<ExpandingFetchSource>();
protected AbstractLoadPlanBuildingAssociationVisitationStrategy(SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory;
this.querySpaces = new QuerySpacesImpl( sessionFactory );
}
protected SessionFactoryImplementor sessionFactory() {
return sessionFactory;
}
@Override
public ExpandingQuerySpaces getQuerySpaces() {
return querySpaces;
}
private void pushToStack(ExpandingFetchSource fetchSource) {
log.trace( "Pushing fetch source to stack : " + fetchSource );
propertyPathStack.push( fetchSource.getPropertyPath() );
fetchSourceStack.addFirst( fetchSource );
}
private ExpandingFetchSource popFromStack() {
final ExpandingFetchSource last = fetchSourceStack.removeFirst();
log.trace( "Popped fetch owner from stack : " + last );
propertyPathStack.pop();
return last;
}
protected ExpandingFetchSource currentSource() {
return fetchSourceStack.peekFirst();
}
@Override
public void start() {
if ( ! fetchSourceStack.isEmpty() ) {
throw new WalkingException(
"Fetch owner stack was not empty on start; " +
"be sure to not use LoadPlanBuilderStrategy instances concurrently"
);
}
propertyPathStack.push( new PropertyPath() );
}
@Override
public void finish() {
propertyPathStack.pop();
MDC.remove( MDC_KEY );
fetchSourceStack.clear();
}
protected abstract void addRootReturn(Return rootReturn);
protected boolean supportsRootEntityReturns() {
return true;
}
@Override
public void startingEntity(EntityDefinition entityDefinition) {
final boolean isRoot = fetchSourceStack.isEmpty();
if ( ! isRoot ) {
return;
}
log.tracef(
"%s Starting root entity : %s",
StringHelper.repeat( ">>", fetchSourceStack.size() ),
entityDefinition.getEntityPersister().getEntityName()
);
if ( !supportsRootEntityReturns() ) {
throw new HibernateException( "This strategy does not support root entity returns" );
}
final EntityReturnImpl entityReturn = new EntityReturnImpl( entityDefinition, querySpaces );
addRootReturn( entityReturn );
pushToStack( entityReturn );
final Joinable entityPersister = (Joinable) entityDefinition.getEntityPersister();
associationKeyRegistered(
new AssociationKey( entityPersister.getTableName(), entityPersister.getKeyColumnNames() )
);
}
@Override
public void finishingEntity(EntityDefinition entityDefinition) {
final FetchSource currentSource = currentSource();
final boolean isRoot = EntityReturn.class.isInstance( currentSource ) &&
entityDefinition.getEntityPersister().equals( EntityReturn.class.cast( currentSource ).getEntityPersister() );
if ( !isRoot ) {
return;
}
final ExpandingFetchSource popped = popFromStack();
checkPoppedEntity( popped, entityDefinition );
log.tracef(
"%s Finished root entity : %s",
StringHelper.repeat( "<<", fetchSourceStack.size() ),
entityDefinition.getEntityPersister().getEntityName()
);
}
private void checkPoppedEntity(ExpandingFetchSource fetchSource, EntityDefinition entityDefinition) {
if ( ! EntityReference.class.isInstance( fetchSource ) ) {
throw new WalkingException(
String.format(
"Mismatched FetchSource from stack on pop. Expecting EntityReference(%s), but found %s",
entityDefinition.getEntityPersister().getEntityName(),
fetchSource
)
);
}
final EntityReference entityReference = (EntityReference) fetchSource;
if ( ! entityReference.getEntityPersister().equals( entityDefinition.getEntityPersister() ) ) {
throw new WalkingException( "Mismatched FetchSource from stack on pop" );
}
}
@Override
public void startingEntityIdentifier(EntityIdentifierDefinition entityIdentifierDefinition) {
log.tracef(
"%s Starting entity identifier : %s",
StringHelper.repeat( ">>", fetchSourceStack.size() ),
entityIdentifierDefinition.getEntityDefinition().getEntityPersister().getEntityName()
);
final EntityReference entityReference = (EntityReference) currentSource();
if ( ! entityReference.getEntityPersister().equals( entityIdentifierDefinition.getEntityDefinition().getEntityPersister() ) ) {
throw new WalkingException(
String.format(
"Encountered unexpected fetch owner [%s] in stack while processing entity identifier for [%s]",
entityReference.getEntityPersister().getEntityName(),
entityIdentifierDefinition.getEntityDefinition().getEntityPersister().getEntityName()
)
);
}
if ( ExpandingEntityIdentifierDescription.class.isInstance( entityReference.getIdentifierDescription() ) ) {
pushToStack( (ExpandingEntityIdentifierDescription) entityReference.getIdentifierDescription() );
}
}
@Override
public void finishingEntityIdentifier(EntityIdentifierDefinition entityIdentifierDefinition) {
final ExpandingFetchSource currentSource = currentSource();
if ( ! ExpandingEntityIdentifierDescription.class.isInstance( currentSource ) ) {
if ( ! EntityReference.class.isInstance( currentSource ) ) {
throw new WalkingException( "Unexpected state in FetchSource stack" );
}
final EntityReference entityReference = (EntityReference) currentSource;
if ( entityReference.getEntityPersister().getEntityKeyDefinition() != entityIdentifierDefinition ) {
throw new WalkingException(
String.format(
"Encountered unexpected fetch owner [%s] in stack while processing entity identifier for [%s]",
entityReference.getEntityPersister().getEntityName(),
entityIdentifierDefinition.getEntityDefinition().getEntityPersister().getEntityName()
)
);
}
return;
}
final ExpandingEntityIdentifierDescription identifierDescription =
(ExpandingEntityIdentifierDescription) popFromStack();
final ExpandingFetchSource entitySource = currentSource();
if ( ! EntityReference.class.isInstance( entitySource ) ) {
throw new WalkingException( "Unexpected state in FetchSource stack" );
}
final EntityReference entityReference = (EntityReference) entitySource;
if ( entityReference.getIdentifierDescription() != identifierDescription ) {
throw new WalkingException(
String.format(
"Encountered unexpected fetch owner [%s] in stack while processing entity identifier for [%s]",
entityReference.getEntityPersister().getEntityName(),
entityIdentifierDefinition.getEntityDefinition().getEntityPersister().getEntityName()
)
);
}
log.tracef(
"%s Finished entity identifier : %s",
StringHelper.repeat( "<<", fetchSourceStack.size() ),
entityIdentifierDefinition.getEntityDefinition().getEntityPersister().getEntityName()
);
}
private ArrayDeque<CollectionReference> collectionReferenceStack = new ArrayDeque<CollectionReference>();
private void pushToCollectionStack(CollectionReference collectionReference) {
log.trace( "Pushing collection reference to stack : " + collectionReference );
propertyPathStack.push( collectionReference.getPropertyPath() );
collectionReferenceStack.addFirst( collectionReference );
}
private CollectionReference popFromCollectionStack() {
final CollectionReference last = collectionReferenceStack.removeFirst();
log.trace( "Popped collection reference from stack : " + last );
propertyPathStack.pop();
return last;
}
private CollectionReference currentCollection() {
return collectionReferenceStack.peekFirst();
}
@Override
public void startingCollection(CollectionDefinition collectionDefinition) {
final boolean isRoot = fetchSourceStack.isEmpty();
if ( ! isRoot ) {
return;
}
log.tracef(
"%s Starting root collection : %s",
StringHelper.repeat( ">>", fetchSourceStack.size() ),
collectionDefinition.getCollectionPersister().getRole()
);
if ( ! supportsRootCollectionReturns() ) {
throw new HibernateException( "This strategy does not support root collection returns" );
}
final CollectionReturn collectionReturn = new CollectionReturnImpl( collectionDefinition, querySpaces );
pushToCollectionStack( collectionReturn );
addRootReturn( collectionReturn );
associationKeyRegistered(
new AssociationKey(
( (Joinable) collectionDefinition.getCollectionPersister() ).getTableName(),
( (Joinable) collectionDefinition.getCollectionPersister() ).getKeyColumnNames()
)
);
}
protected boolean supportsRootCollectionReturns() {
return true;
}
@Override
public void finishingCollection(CollectionDefinition collectionDefinition) {
final boolean isRoot = fetchSourceStack.isEmpty() && collectionReferenceStack.size() == 1;
if ( !isRoot ) {
return;
}
final CollectionReference popped = popFromCollectionStack();
checkedPoppedCollection( popped, collectionDefinition );
log.tracef(
"%s Finished root collection : %s",
StringHelper.repeat( "<<", fetchSourceStack.size() ),
collectionDefinition.getCollectionPersister().getRole()
);
}
private void checkedPoppedCollection(CollectionReference poppedCollectionReference, CollectionDefinition collectionDefinition) {
if ( ! poppedCollectionReference.getCollectionPersister().equals( collectionDefinition.getCollectionPersister() ) ) {
throw new WalkingException( "Mismatched CollectionReference from stack on pop" );
}
}
@Override
public void startingCollectionIndex(CollectionIndexDefinition indexDefinition) {
final Type indexType = indexDefinition.getType();
log.tracef(
"%s Starting collection index graph : %s",
StringHelper.repeat( ">>", fetchSourceStack.size() ),
indexDefinition.getCollectionDefinition().getCollectionPersister().getRole()
);
final CollectionReference collectionReference = currentCollection();
final CollectionFetchableIndex indexGraph = collectionReference.getIndexGraph();
if ( indexType.isEntityType() || indexType.isComponentType() ) {
if ( indexGraph == null ) {
throw new WalkingException(
"CollectionReference did not return an expected index graph : " +
indexDefinition.getCollectionDefinition().getCollectionPersister().getRole()
);
}
if ( !indexType.isAnyType() ) {
pushToStack( (ExpandingFetchSource) indexGraph );
}
}
else {
if ( indexGraph != null ) {
throw new WalkingException(
"CollectionReference returned an unexpected index graph : " +
indexDefinition.getCollectionDefinition().getCollectionPersister().getRole()
);
}
}
}
@Override
public void finishingCollectionIndex(CollectionIndexDefinition indexDefinition) {
final Type indexType = indexDefinition.getType();
if ( indexType.isAnyType() ) {
}
else if ( indexType.isEntityType() || indexType.isComponentType() ) {
final ExpandingFetchSource fetchSource = popFromStack();
if ( !CollectionFetchableIndex.class.isInstance( fetchSource ) ) {
throw new WalkingException(
"CollectionReference did not return an expected index graph : " +
indexDefinition.getCollectionDefinition().getCollectionPersister().getRole()
);
}
}
log.tracef(
"%s Finished collection index graph : %s",
StringHelper.repeat( "<<", fetchSourceStack.size() ),
indexDefinition.getCollectionDefinition().getCollectionPersister().getRole()
);
}
@Override
public void startingCollectionElements(CollectionElementDefinition elementDefinition) {
final Type elementType = elementDefinition.getType();
log.tracef(
"%s Starting collection element graph : %s",
StringHelper.repeat( ">>", fetchSourceStack.size() ),
elementDefinition.getCollectionDefinition().getCollectionPersister().getRole()
);
final CollectionReference collectionReference = currentCollection();
final CollectionFetchableElement elementGraph = collectionReference.getElementGraph();
if ( elementType.isAssociationType() || elementType.isComponentType() ) {
if ( elementGraph == null ) {
throw new IllegalStateException(
"CollectionReference did not return an expected element graph : " +
elementDefinition.getCollectionDefinition().getCollectionPersister().getRole()
);
}
if ( !elementType.isAnyType() ) {
pushToStack( (ExpandingFetchSource) elementGraph );
}
}
else {
if ( elementGraph != null ) {
throw new IllegalStateException(
"CollectionReference returned an unexpected element graph : " +
elementDefinition.getCollectionDefinition().getCollectionPersister().getRole()
);
}
}
}
@Override
public void finishingCollectionElements(CollectionElementDefinition elementDefinition) {
final Type elementType = elementDefinition.getType();
if ( elementType.isAnyType() ) {
}
else if ( elementType.isComponentType() || elementType.isAssociationType()) {
final ExpandingFetchSource popped = popFromStack();
if ( ! CollectionFetchableElement.class.isInstance( popped ) ) {
throw new WalkingException( "Mismatched FetchSource from stack on pop" );
}
}
log.tracef(
"%s Finished collection element graph : %s",
StringHelper.repeat( "<<", fetchSourceStack.size() ),
elementDefinition.getCollectionDefinition().getCollectionPersister().getRole()
);
}
@Override
public void startingComposite(CompositionDefinition compositionDefinition) {
log.tracef(
"%s Starting composite : %s",
StringHelper.repeat( ">>", fetchSourceStack.size() ),
compositionDefinition.getName()
);
if ( fetchSourceStack.isEmpty() && collectionReferenceStack.isEmpty() ) {
throw new HibernateException( "A component cannot be the root of a walk nor a graph" );
}
final FetchSource currentSource = currentSource();
if ( !CompositeFetch.class.isInstance( currentSource ) &&
!CollectionFetchableElement.class.isInstance( currentSource ) &&
!CollectionFetchableIndex.class.isInstance( currentSource ) &&
!ExpandingEntityIdentifierDescription.class.isInstance( currentSource ) ) {
throw new WalkingException( "Mismatched FetchSource from stack on pop" );
}
}
@Override
public void finishingComposite(CompositionDefinition compositionDefinition) {
log.tracef(
"%s Finishing composite : %s",
StringHelper.repeat( "<<", fetchSourceStack.size() ),
compositionDefinition.getName()
);
}
protected PropertyPath currentPropertyPath = new PropertyPath( "" );
@Override
public boolean startingAttribute(AttributeDefinition attributeDefinition) {
log.tracef(
"%s Starting attribute %s",
StringHelper.repeat( ">>", fetchSourceStack.size() ),
attributeDefinition
);
final Type attributeType = attributeDefinition.getType();
final boolean isComponentType = attributeType.isComponentType();
final boolean isAssociationType = attributeType.isAssociationType();
final boolean isBasicType = ! ( isComponentType || isAssociationType );
currentPropertyPath = currentPropertyPath.append( attributeDefinition.getName() );
if ( isBasicType ) {
return true;
}
else if ( isAssociationType ) {
return handleAssociationAttribute( (AssociationAttributeDefinition) attributeDefinition );
}
else {
return handleCompositeAttribute( attributeDefinition );
}
}
@Override
public void finishingAttribute(AttributeDefinition attributeDefinition) {
final Type attributeType = attributeDefinition.getType();
if ( attributeType.isAssociationType() ) {
final AssociationAttributeDefinition associationAttributeDefinition =
(AssociationAttributeDefinition) attributeDefinition;
if ( attributeType.isAnyType() ) {
}
else if ( attributeType.isEntityType() ) {
final ExpandingFetchSource source = currentSource();
if ( AttributeFetch.class.isInstance( source ) &&
associationAttributeDefinition.equals( AttributeFetch.class.cast( source ).getFetchedAttributeDefinition() ) ) {
final ExpandingFetchSource popped = popFromStack();
checkPoppedEntity( popped, associationAttributeDefinition.toEntityDefinition() );
}
}
else if ( attributeType.isCollectionType() ) {
final CollectionReference currentCollection = currentCollection();
if ( AttributeFetch.class.isInstance( currentCollection ) &&
associationAttributeDefinition.equals( AttributeFetch.class.cast( currentCollection ).getFetchedAttributeDefinition() ) ) {
final CollectionReference popped = popFromCollectionStack();
checkedPoppedCollection( popped, associationAttributeDefinition.toCollectionDefinition() );
}
}
}
else if ( attributeType.isComponentType() ) {
final ExpandingFetchSource popped = popFromStack();
if ( !CompositeAttributeFetch.class.isInstance( popped ) ) {
throw new WalkingException(
String.format(
"Mismatched FetchSource from stack on pop; expected: CompositeAttributeFetch; actual: [%s]",
popped
)
);
}
final CompositeAttributeFetch poppedAsCompositeAttributeFetch = (CompositeAttributeFetch) popped;
if ( !attributeDefinition.equals( poppedAsCompositeAttributeFetch.getFetchedAttributeDefinition() ) ) {
throw new WalkingException(
String.format(
"Mismatched CompositeAttributeFetch from stack on pop; expected fetch for attribute: [%s]; actual: [%s]",
attributeDefinition,
poppedAsCompositeAttributeFetch.getFetchedAttributeDefinition()
)
);
}
}
log.tracef(
"%s Finishing up attribute : %s",
StringHelper.repeat( "<<", fetchSourceStack.size() ),
attributeDefinition
);
currentPropertyPath = currentPropertyPath.getParent();
}
private Map<AssociationKey,FetchSource> fetchedAssociationKeySourceMap = new HashMap<AssociationKey, FetchSource>();
@Override
public boolean isDuplicateAssociationKey(AssociationKey associationKey) {
return fetchedAssociationKeySourceMap.containsKey( associationKey );
}
@Override
public void associationKeyRegistered(AssociationKey associationKey) {
log.tracef(
"%s Registering AssociationKey : %s -> %s",
StringHelper.repeat( "..", fetchSourceStack.size() ),
associationKey,
currentSource()
);
fetchedAssociationKeySourceMap.put( associationKey, currentSource() );
}
@Override
public FetchSource registeredFetchSource(AssociationKey associationKey) {
return fetchedAssociationKeySourceMap.get( associationKey );
}
@Override
public void foundCircularAssociation(AssociationAttributeDefinition attributeDefinition) {
final FetchStrategy fetchStrategy = determineFetchStrategy( attributeDefinition );
if ( fetchStrategy.getStyle() != FetchStyle.JOIN ) {
return;
}
final AssociationKey associationKey = attributeDefinition.getAssociationKey();
if ( attributeDefinition.getAssociationNature() == AssociationAttributeDefinition.AssociationNature.ENTITY ) {
final Joinable currentEntityPersister = (Joinable) currentSource().resolveEntityReference().getEntityPersister();
final AssociationKey currentEntityReferenceAssociationKey =
new AssociationKey( currentEntityPersister.getTableName(), currentEntityPersister.getKeyColumnNames() );
final FetchSource registeredFetchSource = registeredFetchSource( associationKey );
if ( registeredFetchSource != null && ! associationKey.equals( currentEntityReferenceAssociationKey ) ) {
currentSource().buildBidirectionalEntityReference(
attributeDefinition,
fetchStrategy,
registeredFetchSource( associationKey ).resolveEntityReference()
);
}
}
else {
}
}
@Override
public void foundAny(AnyMappingDefinition anyDefinition) {
}
protected boolean handleCompositeAttribute(AttributeDefinition attributeDefinition) {
final CompositeFetch compositeFetch = currentSource().buildCompositeAttributeFetch( attributeDefinition );
pushToStack( (ExpandingFetchSource) compositeFetch );
return true;
}
protected boolean handleAssociationAttribute(AssociationAttributeDefinition attributeDefinition) {
final FetchStrategy fetchStrategy = determineFetchStrategy( attributeDefinition );
final ExpandingFetchSource currentSource = currentSource();
currentSource.validateFetchPlan( fetchStrategy, attributeDefinition );
final AssociationAttributeDefinition.AssociationNature nature = attributeDefinition.getAssociationNature();
if ( nature == AssociationAttributeDefinition.AssociationNature.ANY ) {
currentSource.buildAnyAttributeFetch(
attributeDefinition,
fetchStrategy
);
return false;
}
else if ( nature == AssociationAttributeDefinition.AssociationNature.ENTITY ) {
EntityFetch fetch = currentSource.buildEntityAttributeFetch(
attributeDefinition,
fetchStrategy
);
if ( FetchStrategyHelper.isJoinFetched( fetchStrategy ) ) {
pushToStack( (ExpandingFetchSource) fetch );
return true;
}
else {
return false;
}
}
else {
CollectionAttributeFetch fetch = currentSource.buildCollectionAttributeFetch(
attributeDefinition,
fetchStrategy
);
if ( FetchStrategyHelper.isJoinFetched( fetchStrategy ) ) {
pushToCollectionStack( fetch );
return true;
}
else {
return false;
}
}
}
protected abstract FetchStrategy determineFetchStrategy(AssociationAttributeDefinition attributeDefinition);
protected int currentDepth() {
return fetchSourceStack.size();
}
protected boolean isTooManyCollections() {
return false;
}
@Override
public SessionFactoryImplementor getSessionFactory() {
return sessionFactory();
}
public static class PropertyPathStack {
private ArrayDeque<PropertyPath> pathStack = new ArrayDeque<PropertyPath>();
public void push(PropertyPath path) {
pathStack.addFirst( path );
MDC.put( MDC_KEY, extractFullPath( path ) );
}
private String (PropertyPath path) {
return path == null ? "<no-path>" : path.getFullPath();
}
public void pop() {
pathStack.removeFirst();
PropertyPath newHead = pathStack.peekFirst();
MDC.put( MDC_KEY, extractFullPath( newHead ) );
}
}
}