diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java index a8c174352e..8a2d92f255 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java @@ -141,6 +141,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, private final Map entityBindingMap = new HashMap<>(); private final List composites = new ArrayList<>(); + private final Map, Component> genericComponentsMap = new HashMap<>(); private final Map collectionBindingMap = new HashMap<>(); private final Map filterDefinitionMap = new HashMap<>(); @@ -282,6 +283,16 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, composites.forEach( consumer ); } + @Override + public void registerGenericComponent(Component component) { + genericComponentsMap.put( component.getComponentClass(), component ); + } + + @Override + public Component getGenericComponent(Class componentClass) { + return genericComponentsMap.get( componentClass ); + } + @Override public SessionFactoryBuilder getSessionFactoryBuilder() { throw new UnsupportedOperationException( @@ -2314,6 +2325,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, options, entityBindingMap, composites, + genericComponentsMap, mappedSuperClasses, collectionBindingMap, typeDefRegistry.copyRegistrationMap(), diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java index 932074fb62..2bac584335 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java @@ -90,6 +90,7 @@ public class MetadataImpl implements MetadataImplementor, Serializable { private final Map entityBindingMap; private final List composites; + private final Map, Component> genericComponentsMap; private final Map, MappedSuperclass> mappedSuperclassMap; private final Map collectionBindingMap; private final Map typeDefinitionMap; @@ -110,6 +111,7 @@ public class MetadataImpl implements MetadataImplementor, Serializable { MetadataBuildingOptions metadataBuildingOptions, Map entityBindingMap, List composites, + Map, Component> genericComponentsMap, Map, MappedSuperclass> mappedSuperclassMap, Map collectionBindingMap, Map typeDefinitionMap, @@ -129,6 +131,7 @@ public class MetadataImpl implements MetadataImplementor, Serializable { this.metadataBuildingOptions = metadataBuildingOptions; this.entityBindingMap = entityBindingMap; this.composites = composites; + this.genericComponentsMap = genericComponentsMap; this.mappedSuperclassMap = mappedSuperclassMap; this.collectionBindingMap = collectionBindingMap; this.typeDefinitionMap = typeDefinitionMap; @@ -570,6 +573,11 @@ public class MetadataImpl implements MetadataImplementor, Serializable { composites.forEach( consumer ); } + @Override + public Component getGenericComponent(Class componentClass) { + return genericComponentsMap.get( componentClass ); + } + @Override public org.hibernate.type.Type getIdentifierType(String entityName) throws MappingException { final PersistentClass pc = entityBindingMap.get( entityName ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java index 1bbf946cd8..64f02e8c13 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java @@ -229,7 +229,39 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { return join; } + /** + * Embeddable classes can be defined using generics. For this reason, we must check + * every property value and specially handle generic components by setting the property + * as generic, to later be able to resolve its concrete type, and creating a new component + * with correctly typed sub-properties for the metamodel. + */ + public static void handleGenericComponentProperty(Property property, MetadataBuildingContext context) { + final Value value = property.getValue(); + if ( value instanceof Component ) { + final Component component = (Component) value; + if ( component.isGeneric() && context.getMetadataCollector() + .getGenericComponent( component.getComponentClass() ) == null ) { + // If we didn't already, register the generic component to use it later + // as the metamodel type for generic embeddable attributes + final Component copy = component.copy(); + copy.setGeneric( false ); + copy.getProperties().clear(); + for ( Property prop : component.getProperties() ) { + prepareActualProperty( + prop, + component.getComponentClass(), + true, + context, + copy::addProperty + ); + } + context.getMetadataCollector().registerGenericComponent( copy ); + } + } + } + private void addPropertyToPersistentClass(Property property, XClass declaringClass) { + handleGenericComponentProperty( property, getContext() ); if ( declaringClass != null ) { final InheritanceState inheritanceState = inheritanceStatePerClass.get( declaringClass ); if ( inheritanceState == null ) { @@ -253,10 +285,10 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { private void addPropertyToMappedSuperclass(Property prop, XClass declaringClass) { final Class type = getContext().getBootstrapContext().getReflectionManager().toClass( declaringClass ); final MappedSuperclass superclass = getContext().getMetadataCollector().getMappedSuperclass( type ); - prepareActualPropertyForSuperclass( prop, type, true, getContext(), superclass::addDeclaredProperty ); + prepareActualProperty( prop, type, true, getContext(), superclass::addDeclaredProperty ); } - static void prepareActualPropertyForSuperclass( + static void prepareActualProperty( Property prop, Class type, boolean allowCollections, @@ -322,14 +354,20 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { } if ( value instanceof Component ) { final Component component = ( (Component) value ); - final Iterator propertyIterator = component.getPropertyIterator(); - while ( propertyIterator.hasNext() ) { - Property property = propertyIterator.next(); - try { - property.getGetter( component.getComponentClass() ); - } - catch (PropertyNotFoundException e) { - propertyIterator.remove(); + final Class componentClass = component.getComponentClass(); + if ( component.isGeneric() ) { + actualProperty.setValue( context.getMetadataCollector().getGenericComponent( componentClass ) ); + } + else { + final Iterator propertyIterator = component.getPropertyIterator(); + while ( propertyIterator.hasNext() ) { + Property property = propertyIterator.next(); + try { + property.getGetter( componentClass ); + } + catch (PropertyNotFoundException e) { + propertyIterator.remove(); + } } } } @@ -366,9 +404,8 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { } else if ( value instanceof Component ) { final Component component = (Component) value; - // Avoid setting component class name to java.lang.Object - // for embeddable types with generic type parameters - if ( !typeName.equals( Object.class.getName() ) ) { + // Avoid setting type name for generic components + if ( !component.isGeneric() ) { component.setComponentClassName( typeName ); } if ( component.getTypeName() != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java index 1a1ef41a30..0273edfd5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java @@ -8,7 +8,6 @@ package org.hibernate.boot.model.internal; import java.lang.annotation.Annotation; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -29,7 +28,6 @@ import jakarta.persistence.Version; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.MappingException; -import org.hibernate.PropertyNotFoundException; import org.hibernate.annotations.Any; import org.hibernate.annotations.AttributeBinderType; import org.hibernate.annotations.CompositeType; @@ -71,6 +69,8 @@ import static jakarta.persistence.FetchType.LAZY; import static org.hibernate.boot.model.internal.AnyBinder.bindAny; import static org.hibernate.boot.model.internal.BinderHelper.isCompositeId; import static org.hibernate.boot.model.internal.BinderHelper.isGlobalGeneratorNameGlobal; +import static org.hibernate.boot.model.internal.ClassPropertyHolder.handleGenericComponentProperty; +import static org.hibernate.boot.model.internal.ClassPropertyHolder.prepareActualProperty; import static org.hibernate.boot.model.internal.CollectionBinder.bindCollection; import static org.hibernate.boot.model.internal.GeneratorBinder.createForeignGenerator; import static org.hibernate.boot.model.internal.GeneratorBinder.createIdGenerator; @@ -339,18 +339,13 @@ public class PropertyBinder { } private void setDeclaredIdentifier(RootClass rootClass, MappedSuperclass superclass, Property prop) { + handleGenericComponentProperty( prop, buildingContext ); if ( superclass == null ) { rootClass.setDeclaredIdentifierProperty( prop ); return; } final Class type = buildingContext.getBootstrapContext().getReflectionManager().toClass( declaringClass ); - ClassPropertyHolder.prepareActualPropertyForSuperclass( - prop, - type, - false, - buildingContext, - superclass::setDeclaredIdentifierProperty - ); + prepareActualProperty( prop, type, false, buildingContext, superclass::setDeclaredIdentifierProperty ); } private Component getOrCreateCompositeId(RootClass rootClass) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java index de2ca65206..b9558c194d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java @@ -244,6 +244,10 @@ public abstract class AbstractDelegatingMetadata implements MetadataImplementor delegate().visitRegisteredComponents( consumer ); } + @Override + public Component getGenericComponent(Class componentClass) { + return delegate().getGenericComponent( componentClass ); + } @Override public NamedObjectRepository buildNamedQueryRepository(SessionFactoryImplementor sessionFactory) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java index 70cfea48c1..f6bf29d860 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java @@ -83,6 +83,8 @@ public interface InFlightMetadataCollector extends MetadataImplementor { void registerComponent(Component component); + void registerGenericComponent(Component component); + /** * Adds an import (for use in HQL). * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java index 8bb6bdd9ee..eb696d07c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java @@ -56,4 +56,6 @@ public interface MetadataImplementor extends Metadata { void initSessionFactory(SessionFactoryImplementor sessionFactoryImplementor); void visitRegisteredComponents(Consumer consumer); + + Component getGenericComponent(Class componentClass); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java index 06ebddad2c..e480fb2d2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java @@ -67,6 +67,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable private PersistentClass owner; private boolean dynamic; private boolean isKey; + private Boolean isGeneric; private String roleName; private final ArrayList properties = new ArrayList<>(); @@ -123,6 +124,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable this.parentProperty = original.parentProperty; this.owner = original.owner; this.dynamic = original.dynamic; + this.isGeneric = original.isGeneric; this.metaAttributes = original.metaAttributes == null ? null : new HashMap<>( original.metaAttributes ); this.isKey = original.isKey; this.roleName = original.roleName; @@ -819,4 +821,15 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable public void setStructColumnNames(String[] structColumnNames) { this.structColumnNames = structColumnNames; } + + public boolean isGeneric() { + if ( isGeneric == null ) { + isGeneric = getComponentClassName() != null && getComponentClass().getTypeParameters().length != 0; + } + return isGeneric; + } + + public void setGeneric(boolean generic) { + isGeneric = generic; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java index ab6ae582f5..364d19265a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java @@ -300,9 +300,11 @@ public class AttributeFactory { @SuppressWarnings("unchecked") final Class embeddableClass = (Class) component.getComponentClass(); - final EmbeddableDomainType cached = context.locateEmbeddable( embeddableClass, component ); - if ( cached != null ) { - return cached; + if ( !component.isGeneric() ) { + final EmbeddableDomainType cached = context.locateEmbeddable( embeddableClass, component ); + if ( cached != null ) { + return cached; + } } final JavaTypeRegistry registry = context.getTypeConfiguration() diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index e0fa61f210..0a5a9d41e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -14,6 +14,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import org.hibernate.AssertionFailure; import org.hibernate.Internal; @@ -27,6 +28,7 @@ import org.hibernate.mapping.Component; import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.Value; import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.model.domain.AbstractIdentifiableType; import org.hibernate.metamodel.model.domain.BasicDomainType; @@ -251,6 +253,33 @@ public class MetadataContext { return Collections.unmodifiableMap( identifiableTypesByName ); } + private PersistentAttribute buildAttribute( + Property property, + IdentifiableDomainType entityType, + BiFunction, Property, PersistentAttribute> factoryFunction) { + final PersistentAttribute attribute; + final Component component = property.getValue() instanceof Component ? (Component) property.getValue() : null; + if ( component != null && component.isGeneric() ) { + // This is an embeddable property that uses generics, we have to retrieve the generic + // component previously registered and create the concrete attribute + final Component genericComponent = runtimeModelCreationContext.getMetadata() + .getGenericComponent( component.getComponentClass() ); + final Property genericProperty = property.copy(); + genericProperty.setValue( genericComponent ); + genericProperty.setGeneric( true ); + attribute = factoryFunction.apply( entityType, genericProperty ); + if ( !property.isGeneric() ) { + final PersistentAttribute concreteAttribute = factoryFunction.apply( entityType, property ); + //noinspection unchecked + ( (AttributeContainer) entityType ).getInFlightAccess().addConcreteGenericAttribute( concreteAttribute ); + } + } + else { + attribute = factoryFunction.apply( entityType, property ); + } + return attribute; + } + @SuppressWarnings("unchecked") public void wrapUp() { if ( LOG.isTraceEnabled() ) { @@ -286,9 +315,10 @@ public class MetadataContext { // skip the version property, it was already handled previously. continue; } - final PersistentAttribute attribute = attributeFactory.buildAttribute( + final PersistentAttribute attribute = buildAttribute( + property, jpaMapping, - property + attributeFactory::buildAttribute ); if ( attribute != null ) { addAttribute( jpaMapping, attribute ); @@ -329,7 +359,11 @@ public class MetadataContext { // skip the version property, it was already handled previously. continue; } - final PersistentAttribute attribute = attributeFactory.buildAttribute( jpaType, property ); + final PersistentAttribute attribute = buildAttribute( + property, + jpaType, + attributeFactory::buildAttribute + ); if ( attribute != null ) { addAttribute( jpaType, attribute ); if ( property.isNaturalIdentifier() ) { @@ -376,8 +410,9 @@ public class MetadataContext { } ( ( AttributeContainer) embeddable ).getInFlightAccess().finishUp(); - // Do not process embeddables for entity types i.e. id-classes - if ( !( embeddable.getExpressibleJavaType() instanceof EntityJavaType ) ) { + // Do not process embeddables for entity types i.e. id-classes or + // generic component embeddables used just for concrete type resolution + if ( !component.isGeneric() && !( embeddable.getExpressibleJavaType() instanceof EntityJavaType ) ) { embeddables.put( embeddable.getJavaType(), embeddable ); if ( staticMetamodelScanEnabled ) { @@ -423,22 +458,26 @@ public class MetadataContext { //noinspection rawtypes final AttributeContainer attributeContainer = (AttributeContainer) identifiableType; if ( declaredIdentifierProperty != null ) { - final SingularPersistentAttribute idAttribute = attributeFactory.buildIdAttribute( + //noinspection unchecked + final SingularPersistentAttribute idAttribute = (SingularPersistentAttribute) buildAttribute( + declaredIdentifierProperty, identifiableType, - declaredIdentifierProperty + attributeFactory::buildIdAttribute ); //noinspection unchecked attributeContainer.getInFlightAccess().applyIdAttribute( idAttribute ); } - final Property superclassIdentifier = getMappedSuperclassIdentifier( persistentClass ); - if ( superclassIdentifier != null && superclassIdentifier.isGeneric() ) { - // If the superclass identifier is generic we have to build the attribute to register the concrete type - final SingularPersistentAttribute concreteIdentifier = attributeFactory.buildIdAttribute( - identifiableType, - persistentClass.getIdentifierProperty() - ); - //noinspection unchecked - attributeContainer.getInFlightAccess().addConcreteGenericAttribute( concreteIdentifier ); + else { + final Property superclassIdentifier = getMappedSuperclassIdentifier( persistentClass ); + if ( superclassIdentifier != null && superclassIdentifier.isGeneric() ) { + // If the superclass identifier is generic we have to build the attribute to register the concrete type + final SingularPersistentAttribute concreteIdentifier = attributeFactory.buildIdAttribute( + identifiableType, + persistentClass.getIdentifierProperty() + ); + //noinspection unchecked + attributeContainer.getInFlightAccess().addConcreteGenericAttribute( concreteIdentifier ); + } } } else { @@ -519,10 +558,14 @@ public class MetadataContext { if ( mappingType.hasIdentifierProperty() ) { final Property declaredIdentifierProperty = mappingType.getDeclaredIdentifierProperty(); if ( declaredIdentifierProperty != null ) { - final SingularPersistentAttribute attribute = - attributeFactory.buildIdAttribute( jpaMappingType, declaredIdentifierProperty ); //noinspection unchecked - ( ( AttributeContainer) jpaMappingType ).getInFlightAccess().applyIdAttribute( attribute ); + final SingularPersistentAttribute attribute = (SingularPersistentAttribute) buildAttribute( + declaredIdentifierProperty, + jpaMappingType, + attributeFactory::buildIdAttribute + ); + //noinspection unchecked + ( (AttributeContainer) jpaMappingType ).getInFlightAccess().applyIdAttribute( attribute ); } } //a MappedSuperclass can have no identifier if the id is set below in the hierarchy