All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.metamodel.source.annotations.EntityHierarchyBuilder Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 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.metamodel.source.annotations;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.AccessType;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.Index;
import org.jboss.jandex.MethodInfo;

import org.hibernate.AnnotationException;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.binding.InheritanceType;
import org.hibernate.metamodel.source.annotations.entity.EntityClass;
import org.hibernate.metamodel.source.annotations.entity.RootEntitySourceImpl;
import org.hibernate.metamodel.source.annotations.entity.SubclassEntitySourceImpl;
import org.hibernate.metamodel.source.binder.EntityHierarchy;
import org.hibernate.metamodel.source.binder.EntitySource;
import org.hibernate.metamodel.source.binder.SubclassEntitySource;

/**
 * Given a (jandex) annotation index build processes all classes with JPA relevant annotations and pre-orders
 * JPA entities respectively their inheritance hierarchy.
 *
 * @author Hardy Ferentschik
 */
public class EntityHierarchyBuilder {
	private static final DotName OBJECT = DotName.createSimple( Object.class.getName() );

	/**
	 * Pre-processes the annotated entities from the index and create a set of entity hierarchies which can be bound
	 * to the metamodel.
	 *
	 * @param bindingContext The binding context, giving access to needed services and information
	 *
	 * @return a set of {@code EntityHierarchy} instances.
	 */
	public static Set createEntityHierarchies(AnnotationBindingContext bindingContext) {
		Set hierarchies = new HashSet();

		List processedEntities = new ArrayList();
		Map> classToDirectSubClassMap = new HashMap>();
		Index index = bindingContext.getIndex();
		for ( ClassInfo info : index.getKnownClasses() ) {
			if ( !isEntityClass( info ) ) {
				continue;
			}

			if ( processedEntities.contains( info.name() ) ) {
				continue;
			}

			ClassInfo rootClassInfo = findRootEntityClassInfo( index, info );
			List rootClassWithAllSubclasses = new ArrayList();
			// the root entity might have some mapped super classes which we have to take into consideration
			// for inheritance type and default access
			addMappedSuperclasses( index, rootClassInfo, rootClassWithAllSubclasses );

			// collect the current root entity and all its subclasses
			processHierarchy(
					bindingContext,
					rootClassInfo,
					rootClassWithAllSubclasses,
					processedEntities,
					classToDirectSubClassMap
			);

			AccessType defaultAccessType = determineDefaultAccessType( rootClassWithAllSubclasses );
			InheritanceType hierarchyInheritanceType = determineInheritanceType(
					rootClassInfo,
					rootClassWithAllSubclasses
			);

			// create the root entity source
			EntityClass rootEntityClass = new EntityClass(
					rootClassInfo,
					null,
					defaultAccessType,
					hierarchyInheritanceType,
					bindingContext
			);
			RootEntitySourceImpl rootSource = new RootEntitySourceImpl( rootEntityClass );

			addSubclassEntitySources(
					bindingContext,
					classToDirectSubClassMap,
					defaultAccessType,
					hierarchyInheritanceType,
					rootEntityClass,
					rootSource
			);


			hierarchies.add( new EntityHierarchyImpl( rootSource, hierarchyInheritanceType ) );
		}
		return hierarchies;
	}

	private static void addSubclassEntitySources(AnnotationBindingContext bindingContext,
												 Map> classToDirectSubClassMap,
												 AccessType defaultAccessType,
												 InheritanceType hierarchyInheritanceType,
												 EntityClass entityClass,
												 EntitySource entitySource) {
		List subClassInfoList = classToDirectSubClassMap.get( DotName.createSimple( entitySource.getClassName() ) );
		if ( subClassInfoList == null ) {
			return;
		}
		for ( ClassInfo subClassInfo : subClassInfoList ) {
			EntityClass subclassEntityClass = new EntityClass(
					subClassInfo,
					entityClass,
					defaultAccessType,
					hierarchyInheritanceType,
					bindingContext
			);
			SubclassEntitySource subclassEntitySource = new SubclassEntitySourceImpl( subclassEntityClass );
			entitySource.add( subclassEntitySource );
			addSubclassEntitySources(
					bindingContext,
					classToDirectSubClassMap,
					defaultAccessType,
					hierarchyInheritanceType,
					subclassEntityClass,
					subclassEntitySource
			);
		}
	}

	/**
	 * Finds the root entity starting at the entity given by {@code info}. The root entity is not the highest superclass
	 * in a java type sense, but the highest superclass which is also an entity (annotated w/ {@code @Entity}.
	 *
	 * @param index the annotation repository
	 * @param info the class info representing an entity
	 *
	 * @return Finds the root entity starting at the entity given by {@code info}
	 */
	private static ClassInfo findRootEntityClassInfo(Index index, ClassInfo info) {
		ClassInfo rootEntity = info;

		DotName superName = info.superName();
		ClassInfo tmpInfo;
		// walk up the hierarchy until java.lang.Object
		while ( !OBJECT.equals( superName ) ) {
			tmpInfo = index.getClassByName( superName );
			if ( isEntityClass( tmpInfo ) ) {
				rootEntity = tmpInfo;
			}
			superName = tmpInfo.superName();
		}
		return rootEntity;
	}

	private static void addMappedSuperclasses(Index index, ClassInfo info, List classInfoList) {
		DotName superName = info.superName();
		ClassInfo tmpInfo;
		// walk up the hierarchy until java.lang.Object
		while ( !OBJECT.equals( superName ) ) {
			tmpInfo = index.getClassByName( superName );
			if ( isMappedSuperclass( tmpInfo ) ) {
				classInfoList.add( tmpInfo );
			}
			superName = tmpInfo.superName();
		}
	}

	/**
	 * This method does several things.
	 * 
    *
  • Collect all java subclasses (recursive) of {@code classInfo} in {@code rootClassWithAllSubclasses}.
  • *
  • Keeping track of all processed classed annotated with {@code @Entity}
  • *
  • Building up a map of class to direct subclass list
  • *
* * @param bindingContext the binding context * @param classInfo the current class info * @param rootClassWithAllSubclasses used to collect all classes in the hierarchy starting at {@code classInfo} * @param processedEntities Used to keep track of all processed entities * @param classToDirectSubclassMap Create a map of class to direct subclass */ private static void processHierarchy(AnnotationBindingContext bindingContext, ClassInfo classInfo, List rootClassWithAllSubclasses, List processedEntities, Map> classToDirectSubclassMap) { processedEntities.add( classInfo.name() ); rootClassWithAllSubclasses.add( classInfo ); List subClasses = bindingContext.getIndex().getKnownDirectSubclasses( classInfo.name() ); // if there are no more subclasses we reached the leaf class. In order to properly resolve generics we // need to resolve the type information using this leaf class if ( subClasses.isEmpty() ) { bindingContext.resolveAllTypes( classInfo.name().toString() ); } for ( ClassInfo subClassInfo : subClasses ) { addSubClassToSubclassMap( classInfo.name(), subClassInfo, classToDirectSubclassMap ); processHierarchy( bindingContext, subClassInfo, rootClassWithAllSubclasses, processedEntities, classToDirectSubclassMap ); } } private static void addSubClassToSubclassMap(DotName name, ClassInfo subClassInfo, Map> classToDirectSubclassMap) { if ( classToDirectSubclassMap.containsKey( name ) ) { classToDirectSubclassMap.get( name ).add( subClassInfo ); } else { List subclassList = new ArrayList(); subclassList.add( subClassInfo ); classToDirectSubclassMap.put( name, subclassList ); } } /** * Checks whether the class info represents an entity. * * @param info the jandex class info * * @return {@code true} if the class represented by {@code info} is annotated with {@code @Entity}, {@code false} otherwise. */ private static boolean isEntityClass(ClassInfo info) { if ( info == null ) { return false; } // we are only interested in building the class hierarchies for @Entity AnnotationInstance jpaEntityAnnotation = JandexHelper.getSingleAnnotation( info, JPADotNames.ENTITY ); if ( jpaEntityAnnotation == null ) { return false; } // some sanity checks AnnotationInstance mappedSuperClassAnnotation = JandexHelper.getSingleAnnotation( info, JPADotNames.MAPPED_SUPERCLASS ); String className = info.toString(); assertNotEntityAndMappedSuperClass( jpaEntityAnnotation, mappedSuperClassAnnotation, className ); AnnotationInstance embeddableAnnotation = JandexHelper.getSingleAnnotation( info, JPADotNames.EMBEDDABLE ); assertNotEntityAndEmbeddable( jpaEntityAnnotation, embeddableAnnotation, className ); return true; } /** * Checks whether the class info represents a mapped superclass. * * @param info the jandex class info * * @return {@code true} if the class represented by {@code info} is annotated with {@code @MappedSuperclass}, {@code false} otherwise. */ private static boolean isMappedSuperclass(ClassInfo info) { if ( info == null ) { return false; } // we are only interested in building the class hierarchies for @Entity AnnotationInstance mappedSuperclassAnnotation = JandexHelper.getSingleAnnotation( info, JPADotNames.MAPPED_SUPERCLASS ); return mappedSuperclassAnnotation != null; } private static void assertNotEntityAndMappedSuperClass(AnnotationInstance jpaEntityAnnotation, AnnotationInstance mappedSuperClassAnnotation, String className) { if ( jpaEntityAnnotation != null && mappedSuperClassAnnotation != null ) { throw new AnnotationException( "An entity cannot be annotated with both @Entity and @MappedSuperclass. " + className + " has both annotations." ); } } private static void assertNotEntityAndEmbeddable(AnnotationInstance jpaEntityAnnotation, AnnotationInstance embeddableAnnotation, String className) { if ( jpaEntityAnnotation != null && embeddableAnnotation != null ) { throw new AnnotationException( "An entity cannot be annotated with both @Entity and @Embeddable. " + className + " has both annotations." ); } } /** * @param classes the classes in the hierarchy * * @return Returns the default access type for the configured class hierarchy independent of explicit * {@code AccessType} annotations. The default access type is determined by the placement of the * annotations. */ private static AccessType determineDefaultAccessType(List classes) { AccessType accessTypeByEmbeddedIdPlacement = null; AccessType accessTypeByIdPlacement = null; for ( ClassInfo info : classes ) { List idAnnotations = info.annotations().get( JPADotNames.ID ); List embeddedIdAnnotations = info.annotations().get( JPADotNames.EMBEDDED_ID ); if ( CollectionHelper.isNotEmpty( embeddedIdAnnotations ) ) { accessTypeByEmbeddedIdPlacement = determineAccessTypeByIdPlacement( embeddedIdAnnotations ); } if ( CollectionHelper.isNotEmpty( idAnnotations ) ) { accessTypeByIdPlacement = determineAccessTypeByIdPlacement( idAnnotations ); } } if ( accessTypeByEmbeddedIdPlacement != null ) { return accessTypeByEmbeddedIdPlacement; } else if ( accessTypeByIdPlacement != null ) { return accessTypeByIdPlacement; } else { return throwIdNotFoundAnnotationException( classes ); } } private static AccessType determineAccessTypeByIdPlacement(List idAnnotations) { AccessType accessType = null; for ( AnnotationInstance annotation : idAnnotations ) { AccessType tmpAccessType; if ( annotation.target() instanceof FieldInfo ) { tmpAccessType = AccessType.FIELD; } else if ( annotation.target() instanceof MethodInfo ) { tmpAccessType = AccessType.PROPERTY; } else { throw new AnnotationException( "Invalid placement of @Id annotation" ); } if ( accessType == null ) { accessType = tmpAccessType; } else { if ( !accessType.equals( tmpAccessType ) ) { throw new AnnotationException( "Inconsistent placement of @Id annotation within hierarchy " ); } } } return accessType; } private static InheritanceType determineInheritanceType(ClassInfo rootClassInfo, List classes) { if(classes.size() == 1) { return InheritanceType.NO_INHERITANCE; } // if we have more than one entity class the default is SINGLE_TABLE InheritanceType inheritanceType = InheritanceType.SINGLE_TABLE; AnnotationInstance inheritanceAnnotation = JandexHelper.getSingleAnnotation( rootClassInfo, JPADotNames.INHERITANCE ); if ( inheritanceAnnotation != null ) { String enumName = inheritanceAnnotation.value( "strategy" ).asEnum(); javax.persistence.InheritanceType jpaInheritanceType = Enum.valueOf( javax.persistence.InheritanceType.class, enumName ); inheritanceType = InheritanceType.get( jpaInheritanceType ); } // sanity check that the is no other @Inheritance annotation in the hierarchy for ( ClassInfo info : classes ) { if ( rootClassInfo.equals( info ) ) { continue; } inheritanceAnnotation = JandexHelper.getSingleAnnotation( info, JPADotNames.INHERITANCE ); if ( inheritanceAnnotation != null ) { throw new AnnotationException( String.format( "The inheritance type for %s must be specified on the root entity %s", hierarchyListString( classes ), rootClassInfo.name().toString() ) ); } } return inheritanceType; } private static AccessType throwIdNotFoundAnnotationException(List classes) { StringBuilder builder = new StringBuilder(); builder.append( "Unable to determine identifier attribute for class hierarchy consisting of the classe(s) " ); builder.append( hierarchyListString( classes ) ); throw new AnnotationException( builder.toString() ); } private static String hierarchyListString(List classes) { StringBuilder builder = new StringBuilder(); builder.append( "[" ); int count = 0; for ( ClassInfo info : classes ) { builder.append( info.name().toString() ); if ( count < classes.size() - 1 ) { builder.append( ", " ); } count++; } builder.append( "]" ); return builder.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy