From 94b20bafc84d3f26fa37bf41ed79f7be0f75a71c Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 24 Jan 2023 18:07:14 +0100 Subject: [PATCH] HHH-16070 Check for type parameters when setting declared identifier --- .../model/internal/ClassPropertyHolder.java | 4 +- .../boot/model/internal/PropertyBinder.java | 69 +++++++++++++++++-- .../metamodel/internal/AttributeFactory.java | 19 ++++- .../metamodel/internal/MetadataContext.java | 35 ++++++++++ 4 files changed, 115 insertions(+), 12 deletions(-) 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 5f1ef39edb..703efb1876 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 @@ -329,7 +329,7 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { } } - private String getTypeName(Value value) { + static String getTypeName(Value value) { if ( value instanceof Component ) { final Component component = (Component) value; final String typeName = component.getTypeName(); @@ -341,7 +341,7 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { return ( (SimpleValue) value ).getTypeName(); } - private void setTypeName(Value value, String typeName) { + static void setTypeName(Value value, String typeName) { if ( value instanceof ToOne ) { final ToOne toOne = (ToOne) value; toOne.setReferencedEntityName( typeName ); 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 638bd069c5..2d418329e5 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,6 +8,7 @@ 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; @@ -28,6 +29,7 @@ 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; @@ -317,13 +319,7 @@ public class PropertyBinder { inheritanceStatePerClass, buildingContext ); - if ( superclass != null ) { - superclass.setDeclaredIdentifierProperty(property); - } - else { - //we know the property is on the actual entity - rootClass.setDeclaredIdentifierProperty( property ); - } + setDeclaredIdentifier( rootClass, superclass, property ); } } } @@ -342,6 +338,65 @@ public class PropertyBinder { return property; } + private void setDeclaredIdentifier(RootClass rootClass, MappedSuperclass superclass, Property prop) { + if ( superclass == null ) { + rootClass.setDeclaredIdentifierProperty( prop ); + return; + } + // If the type has type parameters, we have to set the declared identifier property on the rootClass + // to be able to retrieve it with the correct type based on type variable assignment in the subclass + final Class type = buildingContext.getBootstrapContext().getReflectionManager().toClass( declaringClass ); + if ( type.getTypeParameters().length == 0 ) { + superclass.setDeclaredIdentifierProperty( prop ); + } + else { + // If the type has type parameters, we have to look up the XClass and actual property again + // because the given XClass has a TypeEnvironment based on the type variable assignments of a subclass + // and that might result in a wrong property type being used for a property which uses a type variable + final XClass actualDeclaringClass = buildingContext.getBootstrapContext().getReflectionManager().toXClass( type ); + for ( XProperty declaredProperty : actualDeclaringClass.getDeclaredProperties( prop.getPropertyAccessorName() ) ) { + if ( prop.getName().equals( declaredProperty.getName() ) ) { + final PropertyData inferredData = new PropertyInferredData( + actualDeclaringClass, + declaredProperty, + null, + buildingContext.getBootstrapContext().getReflectionManager() + ); + final Value originalValue = prop.getValue(); + if ( originalValue instanceof SimpleValue ) { + // Avoid copying when the property doesn't depend on a type variable + if ( inferredData.getTypeName().equals( ClassPropertyHolder.getTypeName( originalValue ) ) ) { + superclass.setDeclaredIdentifierProperty( prop ); + return; + } + } + // If the property depends on a type variable, we have to copy it and the Value + final Property actualProperty = prop.copy(); + actualProperty.setReturnedClassName( inferredData.getTypeName() ); + final Value value = actualProperty.getValue().copy(); + assert !(value instanceof Collection); + ClassPropertyHolder.setTypeName( value, inferredData.getTypeName() ); + if ( value instanceof Component ) { + Component component = ( (Component) value ); + Iterator propertyIterator = component.getPropertyIterator(); + while ( propertyIterator.hasNext() ) { + Property property = propertyIterator.next(); + try { + property.getGetter( component.getComponentClass() ); + } + catch (PropertyNotFoundException e) { + propertyIterator.remove(); + } + } + } + actualProperty.setValue( value ); + superclass.setDeclaredIdentifierProperty( actualProperty ); + break; + } + } + } + } + private Component getOrCreateCompositeId(RootClass rootClass) { final Component id = (Component) rootClass.getIdentifier(); if ( id == null ) { 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 bc866a8834..7e5f3f03c2 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 @@ -583,9 +583,22 @@ public class AttributeFactory { final CompositeTypeImplementor ownerComponentType = (CompositeTypeImplementor) ownerBootDescriptor.getType(); final EmbeddableValuedModelPart ownerMappingModelDescriptor = ownerComponentType.getMappingModelPart(); - final EmbeddableRepresentationStrategy ownerRepStrategy = ownerMappingModelDescriptor - .getEmbeddableTypeDescriptor() - .getRepresentationStrategy(); + final EmbeddableRepresentationStrategy ownerRepStrategy; + + if ( ownerMappingModelDescriptor == null ) { + // When an entity uses a type variable, bound by a mapped superclass, for an embedded id, + // we will not create a model part for the component, but we still need the representation strategy here, + // in order to discover the property members to expose on the JPA metamodel + ownerRepStrategy = ownerBootDescriptor.getBuildingContext() + .getBootstrapContext() + .getRepresentationStrategySelector() + .resolveStrategy( ownerBootDescriptor, null, metadataContext.getRuntimeModelCreationContext() ); + } + else { + ownerRepStrategy = ownerMappingModelDescriptor + .getEmbeddableTypeDescriptor() + .getRepresentationStrategy(); + } if ( ownerRepStrategy.getMode() == RepresentationMode.MAP ) { return new MapMember( 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 dc7747bbe7..7921cec1ab 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 @@ -24,6 +24,7 @@ import org.hibernate.internal.HEMLogging; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.Component; +import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; @@ -428,6 +429,12 @@ public class MetadataContext { //noinspection unchecked rawtypes ( ( AttributeContainer) identifiableType ).getInFlightAccess().applyIdAttribute( idAttribute ); } + else if ( persistentClass.getIdentifier() instanceof Component + && persistentClass.getIdentifierProperty() != getSuperclassIdentifier( persistentClass ) ) { + // If the identifier is a generic component, we have to call buildIdAttribute anyway, + // as this will create and register the EmbeddableType for the subtype + attributeFactory.buildIdAttribute( identifiableType, persistentClass.getIdentifierProperty() ); + } } else { // we have a non-aggregated composite-id @@ -476,6 +483,34 @@ public class MetadataContext { } } + private Property getSuperclassIdentifier(PersistentClass persistentClass) { + final Property declaredIdentifierProperty = persistentClass.getDeclaredIdentifierProperty(); + if ( declaredIdentifierProperty != null ) { + return declaredIdentifierProperty; + } + if ( persistentClass.getSuperMappedSuperclass() != null ) { + return getSuperclassIdentifier( persistentClass.getSuperMappedSuperclass() ); + } + else if ( persistentClass.getSuperclass() != null ) { + return getSuperclassIdentifier( persistentClass.getSuperclass() ); + } + return null; + } + + private Property getSuperclassIdentifier(MappedSuperclass persistentClass) { + final Property declaredIdentifierProperty = persistentClass.getDeclaredIdentifierProperty(); + if ( declaredIdentifierProperty != null ) { + return declaredIdentifierProperty; + } + if ( persistentClass.getSuperMappedSuperclass() != null ) { + return getSuperclassIdentifier( persistentClass.getSuperMappedSuperclass() ); + } + else if ( persistentClass.getSuperPersistentClass() != null ) { + return getSuperclassIdentifier( persistentClass.getSuperPersistentClass() ); + } + return null; + } + private EmbeddableTypeImpl applyIdClassMetadata(Component idClassComponent) { final JavaTypeRegistry registry = getTypeConfiguration() .getJavaTypeRegistry();