/* * 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.boot.model.source.internal.hbm; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.hibernate.HibernateException; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmDiscriminatorSubclassEntityType; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmEntityBaseDefinition; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmHibernateMapping; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmJoinedSubclassEntityType; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmRootEntityType; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmSubclassEntityBaseDefinition; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmUnionSubclassEntityType; import org.hibernate.boot.model.source.spi.EntitySource; import org.hibernate.internal.util.StringHelper; import org.jboss.logging.Logger; /** * Helper for dealing with entity inheritance hierarchies in {@code hbm.xml} * processing. In {@code hbm.xml} the super/sub may be split across documents. * The purpose of this class is to:<ol> * <li> * validate that all hierarchies are complete (make sure a mapping does not reference * an unknown entity as its super) * </li> * <li> * ultimately order the processing of every entity to make sure we process each * hierarchy "downward" (from super to sub(s)). * </li> * </ol> * * @author Steve Ebersole */ public class EntityHierarchyBuilder { private static final Logger log = Logger.getLogger( EntityHierarchyBuilder.class ); private final List<EntityHierarchySourceImpl> entityHierarchyList = new ArrayList<EntityHierarchySourceImpl>(); private final Map<String,AbstractEntitySourceImpl> entitySourceByNameMap = new HashMap<String, AbstractEntitySourceImpl>(); private Map<String,List<ExtendsQueueEntry>> toBeLinkedQueue; public EntityHierarchyBuilder() { } /** * To be called after all mapping documents have been processed (via {@link #indexMappingDocument}) * * @return The built hierarchies * * @throws HibernateException Indicates subclass mappings still waiting to be linked to their super types */ public List<EntityHierarchySourceImpl> buildHierarchies() throws HibernateException { if ( toBeLinkedQueue != null && !toBeLinkedQueue.isEmpty() ) { if ( log.isDebugEnabled() ) { for ( Map.Entry<String, List<ExtendsQueueEntry>> waitingListEntry : toBeLinkedQueue.entrySet() ) { for ( ExtendsQueueEntry waitingEntry : waitingListEntry.getValue() ) { log.debugf( "Entity super-type named as extends [%s] for subclass [%s:%s] not found", waitingListEntry.getKey(), waitingEntry.sourceMappingDocument.getOrigin(), waitingEntry.sourceMappingDocument.determineEntityName( waitingEntry.jaxbSubEntityMapping ) ); } } } throw new HibernateException( "Not all named super-types (extends) were found : " + toBeLinkedQueue.keySet() ); } return entityHierarchyList; } /** * Called for each mapping document. * * @param mappingDocument The {@code hbm.xml} document to index */ public void indexMappingDocument(MappingDocument mappingDocument) { log.tracef( "Indexing mapping document [%s] for purpose of building entity hierarchy ordering", mappingDocument.getOrigin() ); final JaxbHbmHibernateMapping mappingBinding = mappingDocument.getDocumentRoot(); // iterate all root class definitions at the hibernate-mapping level for ( JaxbHbmRootEntityType jaxbRootEntity : mappingBinding.getClazz() ) { // we can immediately handle <class/> elements in terms of creating the hierarchy entry final RootEntitySourceImpl rootEntitySource = new RootEntitySourceImpl( mappingDocument, jaxbRootEntity ); entitySourceByNameMap.put( rootEntitySource.getEntityNamingSource().getEntityName(), rootEntitySource ); final EntityHierarchySourceImpl hierarchy = new EntityHierarchySourceImpl( rootEntitySource ); entityHierarchyList.add( hierarchy ); linkAnyWaiting( mappingDocument, rootEntitySource ); // process any of its nested sub-entity definitions processRootEntitySubEntityElements( mappingDocument, jaxbRootEntity, rootEntitySource ); } // iterate all discriminator-based subclass definitions at the hibernate-mapping level for ( JaxbHbmDiscriminatorSubclassEntityType discriminatorSubclassEntityBinding : mappingBinding.getSubclass() ) { processTopLevelSubClassBinding( mappingDocument, discriminatorSubclassEntityBinding ); } // iterate all joined-subclass definitions at the hibernate-mapping level for ( JaxbHbmJoinedSubclassEntityType joinedSubclassEntityBinding : mappingBinding.getJoinedSubclass() ) { processTopLevelSubClassBinding( mappingDocument, joinedSubclassEntityBinding ); } // iterate all union-subclass definitions at the hibernate-mapping level for ( JaxbHbmUnionSubclassEntityType unionSubclassEntityBinding : mappingBinding.getUnionSubclass() ) { processTopLevelSubClassBinding( mappingDocument, unionSubclassEntityBinding ); } } private void processRootEntitySubEntityElements( MappingDocument mappingDocument, JaxbHbmRootEntityType jaxbRootEntity, RootEntitySourceImpl rootEntitySource) { // todo : technically we should only allow one mutually-exclusive; should we enforce that here? // I believe the DTD/XSD does enforce that, so maybe not a big deal processElements( mappingDocument, jaxbRootEntity.getSubclass(), rootEntitySource ); processElements( mappingDocument, jaxbRootEntity.getJoinedSubclass(), rootEntitySource ); processElements( mappingDocument, jaxbRootEntity.getUnionSubclass(), rootEntitySource ); } private void processSubEntityElements( MappingDocument mappingDocument, JaxbHbmEntityBaseDefinition entityBinding, AbstractEntitySourceImpl container) { if ( JaxbHbmDiscriminatorSubclassEntityType.class.isInstance( entityBinding ) ) { final JaxbHbmDiscriminatorSubclassEntityType jaxbSubclass = (JaxbHbmDiscriminatorSubclassEntityType) entityBinding; processElements( mappingDocument, jaxbSubclass.getSubclass(), container ); } else if ( JaxbHbmJoinedSubclassEntityType.class.isInstance( entityBinding ) ) { final JaxbHbmJoinedSubclassEntityType jaxbJoinedSubclass = (JaxbHbmJoinedSubclassEntityType) entityBinding; processElements( mappingDocument, jaxbJoinedSubclass.getJoinedSubclass(), container ); } else if ( JaxbHbmUnionSubclassEntityType.class.isInstance( entityBinding ) ) { final JaxbHbmUnionSubclassEntityType jaxbUnionSubclass = (JaxbHbmUnionSubclassEntityType) entityBinding; processElements( mappingDocument, jaxbUnionSubclass.getUnionSubclass(), container ); } } private void processElements( MappingDocument mappingDocument, List<? extends JaxbHbmSubclassEntityBaseDefinition> nestedSubEntityList, AbstractEntitySourceImpl container) { for ( final JaxbHbmSubclassEntityBaseDefinition jaxbSubEntity : nestedSubEntityList ) { final SubclassEntitySourceImpl subClassEntitySource = createSubClassEntitySource( mappingDocument, jaxbSubEntity, container ); entitySourceByNameMap.put( subClassEntitySource.getEntityNamingSource().getEntityName(), subClassEntitySource ); container.add( subClassEntitySource ); linkAnyWaiting( mappingDocument, subClassEntitySource ); // Re-run the sub element to handle subclasses within the subclass. processSubEntityElements( mappingDocument, jaxbSubEntity, subClassEntitySource ); } } private SubclassEntitySourceImpl createSubClassEntitySource( MappingDocument mappingDocument, JaxbHbmSubclassEntityBaseDefinition jaxbSubEntity, EntitySource superEntity) { if ( JaxbHbmJoinedSubclassEntityType.class.isInstance( jaxbSubEntity ) ) { return new JoinedSubclassEntitySourceImpl( mappingDocument, JaxbHbmJoinedSubclassEntityType.class.cast( jaxbSubEntity ), superEntity ); } else { return new SubclassEntitySourceImpl( mappingDocument, jaxbSubEntity, superEntity ); } } private void processTopLevelSubClassBinding( MappingDocument mappingDocument, JaxbHbmSubclassEntityBaseDefinition jaxbSubEntityMapping) { final AbstractEntitySourceImpl entityItExtends = locateExtendedEntitySource( mappingDocument, jaxbSubEntityMapping ); if ( entityItExtends == null ) { // we have not seen its declared super-type yet, add it to the queue to be linked up // later when (if) we do addToToBeLinkedQueue( mappingDocument, jaxbSubEntityMapping ); } else { // we have seen its super-type already final SubclassEntitySourceImpl subEntitySource = createSubClassEntitySource( mappingDocument, jaxbSubEntityMapping, entityItExtends ); entitySourceByNameMap.put( subEntitySource.getEntityNamingSource().getEntityName(), subEntitySource ); entityItExtends.add( subEntitySource ); // this may have been a "middle type". So link any sub entities that may be waiting on it linkAnyWaiting( mappingDocument, subEntitySource ); processSubEntityElements( mappingDocument, jaxbSubEntityMapping, subEntitySource ); } } private AbstractEntitySourceImpl locateExtendedEntitySource( MappingDocument mappingDocument, JaxbHbmSubclassEntityBaseDefinition jaxbSubEntityMapping) { // NOTE : extends may refer to either an entity-name or a class-name, we need to check each // first check using entity-name AbstractEntitySourceImpl entityItExtends = entitySourceByNameMap.get( jaxbSubEntityMapping.getExtends() ); if ( entityItExtends == null ) { // next, check using class name entityItExtends = entitySourceByNameMap.get( mappingDocument.qualifyClassName( jaxbSubEntityMapping.getExtends() ) ); } return entityItExtends; } private void addToToBeLinkedQueue( MappingDocument mappingDocument, JaxbHbmSubclassEntityBaseDefinition jaxbSubEntityMapping) { List<ExtendsQueueEntry> waitingList = null; if ( toBeLinkedQueue == null ) { toBeLinkedQueue = new HashMap<String, List<ExtendsQueueEntry>>(); } else { waitingList = toBeLinkedQueue.get( jaxbSubEntityMapping.getExtends() ); } if ( waitingList == null ) { waitingList = new ArrayList<ExtendsQueueEntry>(); toBeLinkedQueue.put( jaxbSubEntityMapping.getExtends(), waitingList ); } waitingList.add( new ExtendsQueueEntry( mappingDocument, jaxbSubEntityMapping ) ); } private void linkAnyWaiting( MappingDocument mappingDocument, AbstractEntitySourceImpl entitySource) { if ( toBeLinkedQueue == null ) { return; } List<ExtendsQueueEntry> waitingList = toBeLinkedQueue.remove( entitySource.jaxbEntityMapping().getEntityName() ); if ( waitingList != null ) { processWaitingSubEntityMappings( entitySource, waitingList ); waitingList.clear(); } if ( StringHelper.isNotEmpty( entitySource.jaxbEntityMapping().getName() ) ) { final String entityClassName = entitySource.jaxbEntityMapping().getName(); waitingList = toBeLinkedQueue.remove( entityClassName ); if ( waitingList != null ) { processWaitingSubEntityMappings( entitySource, waitingList ); waitingList.clear(); } final String qualifiedEntityClassName = mappingDocument.qualifyClassName( entityClassName ); if ( !entityClassName.equals( qualifiedEntityClassName ) ) { waitingList = toBeLinkedQueue.remove( qualifiedEntityClassName ); if ( waitingList != null ) { processWaitingSubEntityMappings( entitySource, waitingList ); waitingList.clear(); } } } } private void processWaitingSubEntityMappings( AbstractEntitySourceImpl entitySource, List<ExtendsQueueEntry> waitingList) { for ( ExtendsQueueEntry entry : waitingList ) { final SubclassEntitySourceImpl subEntitySource = createSubClassEntitySource( entry.sourceMappingDocument, entry.jaxbSubEntityMapping, entitySource ); entitySourceByNameMap.put( subEntitySource.getEntityNamingSource().getEntityName(), subEntitySource ); entitySource.add( subEntitySource ); // this may have been a "middle type". So link any sub entities that may be waiting on it linkAnyWaiting( entry.sourceMappingDocument, subEntitySource ); processSubEntityElements( entry.sourceMappingDocument, entry.jaxbSubEntityMapping, subEntitySource ); } } private static class ExtendsQueueEntry { private final MappingDocument sourceMappingDocument; private final JaxbHbmSubclassEntityBaseDefinition jaxbSubEntityMapping; public ExtendsQueueEntry( MappingDocument sourceMappingDocument, JaxbHbmSubclassEntityBaseDefinition jaxbSubEntityMapping) { this.sourceMappingDocument = sourceMappingDocument; this.jaxbSubEntityMapping = jaxbSubEntityMapping; } } }