/*
 * 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.persister.walking.spi;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.loader.PropertyPath;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.Type;

import org.jboss.logging.Logger;

Implements metamodel graph walking. In layman terms, we are walking the graph of the users domain model as defined/understood by mapped associations.

Initially grew as a part of the re-implementation of the legacy JoinWalker functionality to instead build LoadPlans. But this is really quite simple walking. Interesting events are handled by calling out to implementations of AssociationVisitationStrategy which really provide the real functionality of what we do as we walk.

The visitor will walk the entire metamodel graph (the parts reachable from the given root)!!! It is up to the provided AssociationVisitationStrategy to tell it when to stop. The walker provides the walking; the strategy provides the semantics of what happens at certain points. Its really very similar to parsers and how parsing is generally split between syntax and semantics. Walker walks the syntax (associations, identifiers, etc) and when it calls out to the strategy the strategy then decides the semantics (literally, the meaning).

The visitor will, however, stop if it sees a "duplicate" AssociationKey. In such a case, the walker would call AssociationVisitationStrategy.foundCircularAssociation and stop walking any further down that graph any further.
Author:Steve Ebersole
/** * Implements metamodel graph walking. In layman terms, we are walking the graph of the users domain model as * defined/understood by mapped associations. * <p/> * Initially grew as a part of the re-implementation of the legacy JoinWalker functionality to instead build LoadPlans. * But this is really quite simple walking. Interesting events are handled by calling out to * implementations of {@link AssociationVisitationStrategy} which really provide the real functionality of what we do * as we walk. * <p/> * The visitor will walk the entire metamodel graph (the parts reachable from the given root)!!! It is up to the * provided AssociationVisitationStrategy to tell it when to stop. The walker provides the walking; the strategy * provides the semantics of what happens at certain points. Its really very similar to parsers and how parsing is * generally split between syntax and semantics. Walker walks the syntax (associations, identifiers, etc) and when it * calls out to the strategy the strategy then decides the semantics (literally, the meaning). * <p/> * The visitor will, however, stop if it sees a "duplicate" AssociationKey. In such a case, the walker would call * {@link AssociationVisitationStrategy#foundCircularAssociation} and stop walking any further down that graph any * further. * * @author Steve Ebersole */
public class MetamodelGraphWalker { private static final Logger log = Logger.getLogger( MetamodelGraphWalker.class );
Entry point into walking the model graph of an entity according to its defined metamodel.
Params:
  • strategy – The semantics strategy
  • persister – The persister describing the entity to start walking from
/** * Entry point into walking the model graph of an entity according to its defined metamodel. * * @param strategy The semantics strategy * @param persister The persister describing the entity to start walking from */
public static void visitEntity(AssociationVisitationStrategy strategy, EntityPersister persister) { strategy.start(); try { new MetamodelGraphWalker( strategy, persister.getFactory() ) .visitEntityDefinition( persister ); } finally { strategy.finish(); } }
Entry point into walking the model graph of a collection according to its defined metamodel.
Params:
  • strategy – The semantics strategy
  • persister – The persister describing the collection to start walking from
/** * Entry point into walking the model graph of a collection according to its defined metamodel. * * @param strategy The semantics strategy * @param persister The persister describing the collection to start walking from */
public static void visitCollection(AssociationVisitationStrategy strategy, CollectionPersister persister) { strategy.start(); try { new MetamodelGraphWalker( strategy, persister.getFactory() ) .visitCollectionDefinition( persister ); } finally { strategy.finish(); } } private final AssociationVisitationStrategy strategy; private final SessionFactoryImplementor factory; // todo : add a getDepth() method to PropertyPath private PropertyPath currentPropertyPath = new PropertyPath(); public MetamodelGraphWalker(AssociationVisitationStrategy strategy, SessionFactoryImplementor factory) { this.strategy = strategy; this.factory = factory; } private void visitEntityDefinition(EntityDefinition entityDefinition) { strategy.startingEntity( entityDefinition ); AbstractEntityPersister persister = (AbstractEntityPersister) entityDefinition.getEntityPersister(); visitIdentifierDefinition( entityDefinition.getEntityKeyDefinition() ); visitAttributes( entityDefinition, persister); strategy.finishingEntity( entityDefinition ); } private void visitIdentifierDefinition(EntityIdentifierDefinition identifierDefinition) { strategy.startingEntityIdentifier( identifierDefinition ); // to make encapsulated and non-encapsulated composite identifiers work the same here, we "cheat" here a // little bit and simply walk the attributes of the composite id in both cases. // this works because the LoadPlans already build the top-level composite for composite ids if ( identifierDefinition.isEncapsulated() ) { // in the encapsulated composite id case that means we have a little bit of duplication between here and // visitCompositeDefinition, but in the spirit of consistently handling composite ids, that is much better // solution... final EncapsulatedEntityIdentifierDefinition idAsEncapsulated = (EncapsulatedEntityIdentifierDefinition) identifierDefinition; final AttributeDefinition idAttr = idAsEncapsulated.getAttributeDefinition(); if ( CompositionDefinition.class.isInstance( idAttr ) ) { visitCompositeDefinition( (CompositionDefinition) idAttr ); } } else { // NonEncapsulatedEntityIdentifierDefinition itself is defined as a CompositionDefinition visitCompositeDefinition( (NonEncapsulatedEntityIdentifierDefinition) identifierDefinition ); } strategy.finishingEntityIdentifier( identifierDefinition ); } private void visitAttributes(AttributeSource attributeSource, AbstractEntityPersister sourcePersister) { final Iterable<AttributeDefinition> attributeDefinitions = attributeSource.getAttributes(); if ( attributeDefinitions == null ) { return; } for ( AttributeDefinition attributeDefinition : attributeDefinitions ) { visitAttributeDefinition( attributeDefinition, sourcePersister); } } private void visitAttributeDefinition(AttributeDefinition attributeDefinition, AbstractEntityPersister sourcePersister) { final PropertyPath subPath = currentPropertyPath.append( attributeDefinition.getName() ); log.debug( "Visiting attribute path : " + subPath.getFullPath() ); if ( attributeDefinition.getType().isAssociationType() ) { final AssociationAttributeDefinition associationAttributeDefinition = (AssociationAttributeDefinition) attributeDefinition; final AssociationKey associationKey = associationAttributeDefinition.getAssociationKey(); if ( isDuplicateAssociationKey( associationKey ) ) { log.debug( "Property path deemed to be circular : " + subPath.getFullPath() ); strategy.foundCircularAssociation( associationAttributeDefinition ); // EARLY EXIT!!! return; } if ( sourcePersister != null ) { String[] columns = sourcePersister.toColumns(attributeDefinition.getName()); // Empty columns means that the attribute is not resolvable on this persister if ( columns.length == 0 ) { return; } } } boolean continueWalk = strategy.startingAttribute( attributeDefinition ); if ( continueWalk ) { final PropertyPath old = currentPropertyPath; currentPropertyPath = subPath; try { final Type attributeType = attributeDefinition.getType(); if ( attributeType.isAssociationType() ) { visitAssociation( (AssociationAttributeDefinition) attributeDefinition ); } else if ( attributeType.isComponentType() ) { visitCompositeDefinition( (CompositionDefinition) attributeDefinition ); } } finally { currentPropertyPath = old; } } strategy.finishingAttribute( attributeDefinition ); } private void visitAssociation(AssociationAttributeDefinition attribute) { // todo : do "too deep" checks; but see note about adding depth to PropertyPath // // may also need to better account for "composite fetches" in terms of "depth". addAssociationKey( attribute.getAssociationKey() ); final AssociationAttributeDefinition.AssociationNature nature = attribute.getAssociationNature(); if ( nature == AssociationAttributeDefinition.AssociationNature.ANY ) { visitAnyDefinition( attribute.toAnyDefinition() ); } else if ( nature == AssociationAttributeDefinition.AssociationNature.COLLECTION ) { visitCollectionDefinition( attribute.toCollectionDefinition() ); } else { visitEntityDefinition( attribute.toEntityDefinition() ); } } private void visitAnyDefinition(AnyMappingDefinition anyDefinition) { strategy.foundAny( anyDefinition ); } private void visitCompositeDefinition(CompositionDefinition compositionDefinition) { strategy.startingComposite( compositionDefinition ); visitAttributes( compositionDefinition, null ); strategy.finishingComposite( compositionDefinition ); } private void visitCollectionDefinition(CollectionDefinition collectionDefinition) { strategy.startingCollection( collectionDefinition ); visitCollectionIndex( collectionDefinition ); visitCollectionElements( collectionDefinition ); strategy.finishingCollection( collectionDefinition ); } private void visitCollectionIndex(CollectionDefinition collectionDefinition) { final CollectionIndexDefinition collectionIndexDefinition = collectionDefinition.getIndexDefinition(); if ( collectionIndexDefinition == null ) { return; } strategy.startingCollectionIndex( collectionIndexDefinition ); log.debug( "Visiting index for collection : " + currentPropertyPath.getFullPath() ); currentPropertyPath = currentPropertyPath.append( "<index>" ); try { final Type collectionIndexType = collectionIndexDefinition.getType(); if ( collectionIndexType.isAnyType() ) { visitAnyDefinition( collectionIndexDefinition.toAnyMappingDefinition() ); } else if ( collectionIndexType.isComponentType() ) { visitCompositeDefinition( collectionIndexDefinition.toCompositeDefinition() ); } else if ( collectionIndexType.isAssociationType() ) { visitEntityDefinition( collectionIndexDefinition.toEntityDefinition() ); } } finally { currentPropertyPath = currentPropertyPath.getParent(); } strategy.finishingCollectionIndex( collectionIndexDefinition ); } private void visitCollectionElements(CollectionDefinition collectionDefinition) { final CollectionElementDefinition elementDefinition = collectionDefinition.getElementDefinition(); strategy.startingCollectionElements( elementDefinition ); final Type collectionElementType = elementDefinition.getType(); if ( collectionElementType.isAnyType() ) { visitAnyDefinition( elementDefinition.toAnyMappingDefinition() ); } else if ( collectionElementType.isComponentType() ) { visitCompositeDefinition( elementDefinition.toCompositeElementDefinition() ); } else if ( collectionElementType.isEntityType() ) { if ( ! collectionDefinition.getCollectionPersister().isOneToMany() ) { final QueryableCollection queryableCollection = (QueryableCollection) collectionDefinition.getCollectionPersister(); addAssociationKey( new AssociationKey( queryableCollection.getTableName(), queryableCollection.getElementColumnNames() ) ); } visitEntityDefinition( elementDefinition.toEntityDefinition() ); } strategy.finishingCollectionElements( elementDefinition ); } private final Set<AssociationKey> visitedAssociationKeys = new HashSet<AssociationKey>();
Add association key to indicate the association is being visited.
Params:
  • associationKey – - the association key.
Throws:
  • WalkingException – if the association with the specified association key has already been visited.
/** * Add association key to indicate the association is being visited. * @param associationKey - the association key. * @throws WalkingException if the association with the specified association key * has already been visited. */
protected void addAssociationKey(AssociationKey associationKey) { if ( ! visitedAssociationKeys.add( associationKey ) ) { throw new WalkingException( String.format( "Association has already been visited: %s", associationKey ) ); } strategy.associationKeyRegistered( associationKey ); }
Has an association with the specified key been visited already?
Params:
  • associationKey – - the association key.
Returns:true, if the association with the specified association key has already been visited; false, otherwise.
/** * Has an association with the specified key been visited already? * @param associationKey - the association key. * @return true, if the association with the specified association key has already been visited; * false, otherwise. */
protected boolean isDuplicateAssociationKey(AssociationKey associationKey) { return visitedAssociationKeys.contains( associationKey ) || strategy.isDuplicateAssociationKey( associationKey ); } }