diff --git a/hibernate-models-common/src/main/java/org/hibernate/models/internal/KeyedConsumer.java b/hibernate-models-common/src/main/java/org/hibernate/models/internal/KeyedConsumer.java new file mode 100644 index 0000000..4ffc592 --- /dev/null +++ b/hibernate-models-common/src/main/java/org/hibernate/models/internal/KeyedConsumer.java @@ -0,0 +1,18 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.internal; + +/** + * A consumer, like {@link java.util.function.Consumer}, accepting a key and a value (Map entry e.g.) + * + * @author Christian Beikov + * @author Steve Ebersole + */ +@FunctionalInterface +public interface KeyedConsumer { + void accept(K key, V value); +} diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/ColumnBinder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/ColumnBinder.java new file mode 100644 index 0000000..830736f --- /dev/null +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/ColumnBinder.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.bind.internal; + +import java.util.function.Supplier; + +import org.hibernate.mapping.Column; +import org.hibernate.models.orm.spi.OrmModelBuildingContext; +import org.hibernate.models.source.spi.AnnotationUsage; + +import static org.hibernate.internal.util.NullnessHelper.nullif; + +/** + * @author Steve Ebersole + */ +public class ColumnBinder { + public static Column bindColumn( + AnnotationUsage annotationUsage, + Supplier defaultNameSupplier, + OrmModelBuildingContext modelBuildingContext) { + return bindColumn( + annotationUsage, + defaultNameSupplier, + false, + true, + 255, + 0, + 0, + modelBuildingContext + ); + } + + public static Column bindColumn( + AnnotationUsage annotationUsage, + Supplier defaultNameSupplier, + boolean uniqueByDefault, + boolean nullableByDefault, + long lengthByDefault, + int precisionByDefault, + int scaleByDefault, + OrmModelBuildingContext modelBuildingContext) { + final Column result = new Column(); + result.setName( columnName( annotationUsage, defaultNameSupplier, modelBuildingContext ) ); + + result.setUnique( annotationUsage.getBoolean( "unique", uniqueByDefault ) ); + result.setNullable( annotationUsage.getBoolean( "nullable", nullableByDefault ) ); + result.setSqlType( annotationUsage.getString( "columnDefinition" ) ); + result.setLength( annotationUsage.getLong( "length", lengthByDefault ) ); + result.setPrecision( annotationUsage.getInteger( "precision", precisionByDefault ) ); + result.setScale( annotationUsage.getInteger( "scale", scaleByDefault ) ); + return result; + } + + + private static String columnName( + AnnotationUsage columnAnnotation, + Supplier defaultNameSupplier, + OrmModelBuildingContext modelBuildingContext) { + return nullif( columnAnnotation.getAttributeValue( "name" ), defaultNameSupplier ); + } + + private ColumnBinder() { + } +} diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/EntityBinder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/EntityBinder.java new file mode 100644 index 0000000..922aa66 --- /dev/null +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/EntityBinder.java @@ -0,0 +1,13 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.bind.internal; + +/** + * @author Steve Ebersole + */ +public class EntityBinder { +} diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/HierarchyAttributeProcessor.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/HierarchyAttributeProcessor.java new file mode 100644 index 0000000..70d5238 --- /dev/null +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/HierarchyAttributeProcessor.java @@ -0,0 +1,132 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.bind.internal; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.hibernate.annotations.TenantId; +import org.hibernate.models.ModelsException; +import org.hibernate.models.internal.CollectionHelper; +import org.hibernate.models.orm.spi.AttributeMetadata; +import org.hibernate.models.orm.spi.EntityHierarchy; +import org.hibernate.models.orm.spi.EntityTypeMetadata; +import org.hibernate.models.orm.spi.OrmModelBuildingContext; +import org.hibernate.models.orm.spi.ProcessResult; +import org.hibernate.models.source.spi.AnnotationUsage; +import org.hibernate.models.source.spi.MemberDetails; + +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Id; +import jakarta.persistence.Version; + +/** + * Processes attributes which are conceptually at the level of the hierarchy - id, version, tenant-id + * + * @author Steve Ebersole + */ +public class HierarchyAttributeProcessor { + public static List preBindHierarchyAttributes( + ProcessResult processResult, + OrmModelBuildingContext modelBuildingContext) { + final Set entityHierarchies = processResult.getEntityHierarchies(); + final List hierarchyIdMembers = CollectionHelper.arrayList( entityHierarchies.size() ); + + for ( EntityHierarchy hierarchy : entityHierarchies ) { + final HierarchyAttributeDescriptor hierarchyAttributeDescriptor = new HierarchyAttributeDescriptor( hierarchy ); + hierarchyIdMembers.add( hierarchyAttributeDescriptor ); + + final EntityTypeMetadata hierarchyRoot = hierarchy.getRoot(); + for ( AttributeMetadata attr : hierarchyRoot.getAttributes() ) { + final MemberDetails attrMember = attr.getMember(); + final AnnotationUsage eIdAnn = attrMember.getAnnotationUsage( EmbeddedId.class ); + if ( eIdAnn != null ) { + hierarchyAttributeDescriptor.collectIdAttribute( attr ); + } + + final AnnotationUsage idAnn = attrMember.getAnnotationUsage( Id.class ); + if ( idAnn != null ) { + hierarchyAttributeDescriptor.collectIdAttribute( attr ); + } + + final AnnotationUsage versionAnn = attrMember.getAnnotationUsage( Version.class ); + if ( versionAnn != null ) { + hierarchyAttributeDescriptor.collectVersionAttribute( attr ); + } + + final AnnotationUsage tenantIdAnn = attrMember.getAnnotationUsage( TenantId.class ); + if ( tenantIdAnn != null ) { + hierarchyAttributeDescriptor.collectTenantIdAttribute( attr ); + } + } + } + + return hierarchyIdMembers; + } + + public static class HierarchyAttributeDescriptor { + private final EntityHierarchy entityHierarchy; + + private Object collectedIdAttributes; + private AttributeMetadata versionAttribute; + private AttributeMetadata tenantIdAttribute; + + public HierarchyAttributeDescriptor(EntityHierarchy entityHierarchy) { + this.entityHierarchy = entityHierarchy; + } + + public EntityHierarchy getEntityHierarchy() { + return entityHierarchy; + } + + public Object getCollectedIdAttributes() { + return collectedIdAttributes; + } + + public void collectIdAttribute(AttributeMetadata member) { + assert member != null; + + if ( collectedIdAttributes == null ) { + collectedIdAttributes = member; + } + else if ( collectedIdAttributes instanceof List ) { + //noinspection unchecked,rawtypes + final List membersList = (List) collectedIdAttributes; + membersList.add( member ); + } + else if ( collectedIdAttributes instanceof AttributeMetadata ) { + final ArrayList combined = new ArrayList<>(); + combined.add( (AttributeMetadata) collectedIdAttributes ); + combined.add( member ); + collectedIdAttributes = combined; + } + } + + public AttributeMetadata getVersionAttribute() { + return versionAttribute; + } + + public void collectVersionAttribute(AttributeMetadata member) { + if ( versionAttribute != null ) { + throw new ModelsException( "Encountered multiple @Version attributes for hierarchy " + getEntityHierarchy().getRoot().getEntityName() ); + } + versionAttribute = member; + } + + public AttributeMetadata getTenantIdAttribute() { + return tenantIdAttribute; + } + + public void collectTenantIdAttribute(AttributeMetadata member) { + if ( tenantIdAttribute != null ) { + throw new ModelsException( "Encountered multiple @TenantId attributes for hierarchy " + getEntityHierarchy().getRoot().getEntityName() ); + } + tenantIdAttribute = member; + } + } +} diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/TableBinder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/TableBinder.java new file mode 100644 index 0000000..e28d4e5 --- /dev/null +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/TableBinder.java @@ -0,0 +1,63 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.bind.internal; + +import java.util.function.Supplier; + +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.mapping.Table; +import org.hibernate.models.orm.spi.OrmModelBuildingContext; +import org.hibernate.models.source.spi.AnnotationUsage; + +import static org.hibernate.internal.util.NullnessHelper.nullif; + +/** + * @author Steve Ebersole + */ +public class TableBinder { + public static Table bindTable( + AnnotationUsage tableAnnotation, + Supplier implicitTableNameSupplier, + boolean isAbstract, + MetadataBuildingContext metadataBuildingContext, + OrmModelBuildingContext modelBuildingContext) { + final InFlightMetadataCollector metadataCollector = metadataBuildingContext.getMetadataCollector(); + return metadataCollector.addTable( + schemaName( tableAnnotation, metadataBuildingContext, modelBuildingContext ), + catalogName( tableAnnotation, metadataBuildingContext, modelBuildingContext ), + nullif( tableAnnotation.getString( "name" ), implicitTableNameSupplier ), + null, + isAbstract, + metadataBuildingContext + ); + } + + public static String catalogName( + AnnotationUsage tableAnnotation, + MetadataBuildingContext metadataBuildingContext, + OrmModelBuildingContext modelBuildingContext) { + if ( tableAnnotation != null ) { + return tableAnnotation.getString( "catalog", metadataBuildingContext.getMappingDefaults().getImplicitCatalogName() ); + } + + return metadataBuildingContext.getMappingDefaults().getImplicitCatalogName(); + } + + public static String schemaName( + AnnotationUsage tableAnnotation, + MetadataBuildingContext metadataBuildingContext, + OrmModelBuildingContext modelBuildingContext) { + if ( tableAnnotation != null ) { + return tableAnnotation.getString( "schema", metadataBuildingContext.getMappingDefaults().getImplicitSchemaName() ); + } + return metadataBuildingContext.getMappingDefaults().getImplicitSchemaName(); + } + + private TableBinder() { + } +} diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyImpl.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyImpl.java index 017a238..ec63ddb 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyImpl.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyImpl.java @@ -11,7 +11,9 @@ import org.hibernate.annotations.Cache; import org.hibernate.annotations.NaturalIdCache; +import org.hibernate.annotations.OptimisticLocking; import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.engine.OptimisticLockStyle; import org.hibernate.models.orm.JpaAnnotations; import org.hibernate.models.orm.spi.CacheRegion; import org.hibernate.models.orm.spi.EntityHierarchy; @@ -34,34 +36,82 @@ public class EntityHierarchyImpl implements EntityHierarchy { private final EntityTypeMetadata rootEntityTypeMetadata; private final InheritanceType inheritanceType; + private final OptimisticLockStyle optimisticLockStyle; + private final CacheRegion cacheRegion; private final NaturalIdCacheRegion naturalIdCacheRegion; - // todo : version? optimistic-locking? row-id? tenant-id? others? - public EntityHierarchyImpl( ClassDetails rootEntityClassDetails, jakarta.persistence.AccessType defaultAccessType, AccessType defaultCacheAccessType, Consumer typeConsumer, - OrmModelBuildingContext processingContext) { + OrmModelBuildingContext modelBuildingContext) { this.rootEntityTypeMetadata = new EntityTypeMetadataImpl( rootEntityClassDetails, this, defaultAccessType, typeConsumer, - processingContext + modelBuildingContext ); this.inheritanceType = determineInheritanceType( rootEntityTypeMetadata ); + this.optimisticLockStyle = determineOptimisticLockStyle( rootEntityTypeMetadata ); final AnnotationUsage cacheAnnotation = rootEntityClassDetails.getAnnotationUsage( Cache.class ); final AnnotationUsage naturalIdCacheAnnotation = rootEntityClassDetails.getAnnotationUsage( NaturalIdCache.class ); - cacheRegion = new CacheRegion( cacheAnnotation, defaultCacheAccessType, rootEntityClassDetails.getName() ); naturalIdCacheRegion = new NaturalIdCacheRegion( naturalIdCacheAnnotation, cacheRegion ); } + @Override + public EntityTypeMetadata getRoot() { + return rootEntityTypeMetadata; + } + + @Override + public InheritanceType getInheritanceType() { + return inheritanceType; + } + + @Override + public OptimisticLockStyle getOptimisticLockStyle() { + return optimisticLockStyle; + } + + @Override + public CacheRegion getCacheRegion() { + return cacheRegion; + } + + @Override + public NaturalIdCacheRegion getNaturalIdCacheRegion() { + return naturalIdCacheRegion; + } + + @Override + public String toString() { + return String.format( + Locale.ROOT, + "EntityHierarchy(`%s` (%s))", + rootEntityTypeMetadata.getEntityName(), + inheritanceType.name() + ); + } + + + private static final OptimisticLockStyle DEFAULT_LOCKING_STRATEGY = OptimisticLockStyle.VERSION; + + private OptimisticLockStyle determineOptimisticLockStyle(EntityTypeMetadata rootEntityTypeMetadata) { + final AnnotationUsage lockStrategyAnn = rootEntityTypeMetadata + .getClassDetails() + .getAnnotationUsage( OptimisticLocking.class ); + if ( lockStrategyAnn == null ) { + return DEFAULT_LOCKING_STRATEGY; + } + return lockStrategyAnn.getEnum( "type", DEFAULT_LOCKING_STRATEGY ); + } + private InheritanceType determineInheritanceType(EntityTypeMetadata root) { if ( ORM_MODEL_LOGGER.isDebugEnabled() ) { // Validate that there is no @Inheritance annotation further down the hierarchy @@ -108,33 +158,4 @@ private void ensureNoInheritanceAnnotationsOnSubclasses(IdentifiableTypeMetadata } ); } - @Override - public EntityTypeMetadata getRoot() { - return rootEntityTypeMetadata; - } - - @Override - public InheritanceType getInheritanceType() { - return inheritanceType; - } - - @Override - public CacheRegion getCacheRegion() { - return cacheRegion; - } - - @Override - public NaturalIdCacheRegion getNaturalIdCacheRegion() { - return naturalIdCacheRegion; - } - - @Override - public String toString() { - return String.format( - Locale.ROOT, - "EntityHierarchyImpl(`%s` (%s))", - rootEntityTypeMetadata.getEntityName(), - inheritanceType.name() - ); - } } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityHierarchy.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityHierarchy.java index 13544f7..fa9d9a0 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityHierarchy.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityHierarchy.java @@ -6,6 +6,8 @@ */ package org.hibernate.models.orm.spi; +import org.hibernate.engine.OptimisticLockStyle; + import jakarta.persistence.InheritanceType; /** @@ -25,6 +27,11 @@ public interface EntityHierarchy { */ InheritanceType getInheritanceType(); + /** + * Style of optimistic locking for the hierarchy. + */ + OptimisticLockStyle getOptimisticLockStyle(); + /** * The caching configuration for entities in this hierarchy. */ diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/ProcessResult.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/ProcessResult.java index 9688069..89117bb 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/ProcessResult.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/ProcessResult.java @@ -9,6 +9,8 @@ import java.util.Map; import java.util.Set; +import org.hibernate.models.internal.IndexedConsumer; +import org.hibernate.models.internal.KeyedConsumer; import org.hibernate.models.source.spi.ClassDetails; /** @@ -22,16 +24,47 @@ public interface ProcessResult { */ Set getEntityHierarchies(); + default void forEachEntityHierarchy(IndexedConsumer hierarchyConsumer) { + final Set entityHierarchies = getEntityHierarchies(); + if ( entityHierarchies.isEmpty() ) { + return; + } + + int pos = 0; + for ( EntityHierarchy entityHierarchy : entityHierarchies ) { + hierarchyConsumer.accept( pos, entityHierarchy ); + pos++; + } + } + /** * All mapped-superclasses defined in the persistence unit */ Map getMappedSuperclasses(); + default void forEachMappedSuperclass(KeyedConsumer consumer) { + final Map mappedSuperclasses = getMappedSuperclasses(); + if ( mappedSuperclasses.isEmpty() ) { + return; + } + + mappedSuperclasses.forEach( consumer::accept ); + } + /** * All embeddables defined in the persistence unit */ Map getEmbeddables(); + default void forEachEmbeddable(KeyedConsumer consumer) { + final Map embeddables = getEmbeddables(); + if ( embeddables.isEmpty() ) { + return; + } + + embeddables.forEach( consumer::accept ); + } + /** * Global registrations collected while processing the persistence-unit. */ diff --git a/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/AggregatedIdEntity.java b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/AggregatedIdEntity.java new file mode 100644 index 0000000..f24032f --- /dev/null +++ b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/AggregatedIdEntity.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.bind; + +import org.hibernate.annotations.TenantId; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +/** + * @author Steve Ebersole + */ +@Entity +@Table(name = "agg_id_entities") +public class AggregatedIdEntity { + @EmbeddedId + private Pk id; + + @Version + private Integer version; + @TenantId + private String tenantId; + + @Embeddable + public static class Pk { + private Integer id1; + private Integer id2; + } +} diff --git a/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/HierarchyAttributeProcessorSmokeTests.java b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/HierarchyAttributeProcessorSmokeTests.java new file mode 100644 index 0000000..bb4751d --- /dev/null +++ b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/HierarchyAttributeProcessorSmokeTests.java @@ -0,0 +1,137 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.bind; + +import java.util.List; +import java.util.Set; + +import org.hibernate.annotations.TenantId; +import org.hibernate.models.orm.bind.internal.HierarchyAttributeProcessor; +import org.hibernate.models.orm.bind.internal.HierarchyAttributeProcessor.HierarchyAttributeDescriptor; +import org.hibernate.models.orm.internal.ManagedResourcesImpl; +import org.hibernate.models.orm.process.MyStringConverter; +import org.hibernate.models.orm.process.MyUuidConverter; +import org.hibernate.models.orm.process.Person; +import org.hibernate.models.orm.process.Root; +import org.hibernate.models.orm.process.Sub; +import org.hibernate.models.orm.spi.AttributeMetadata; +import org.hibernate.models.orm.spi.EntityHierarchy; +import org.hibernate.models.orm.spi.ManagedResources; +import org.hibernate.models.orm.spi.ProcessResult; +import org.hibernate.models.orm.spi.Processor; +import org.hibernate.models.orm.util.OrmModelBuildingContextTesting; +import org.hibernate.models.source.SourceModelTestHelper; +import org.hibernate.models.source.internal.SourceModelBuildingContextImpl; +import org.hibernate.models.source.spi.AnnotationUsage; +import org.hibernate.models.source.spi.MemberDetails; + +import org.junit.jupiter.api.Test; + +import org.jboss.jandex.Index; + +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Id; +import jakarta.persistence.Version; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.models.internal.SimpleClassLoading.SIMPLE_CLASS_LOADING; + +/** + * @author Steve Ebersole + */ +public class HierarchyAttributeProcessorSmokeTests { + @Test + void testSimpleId() { + final List hierarchyAttributeDescriptors = buildHierarchyAttributes( SimpleIdEntity.class ); + + hierarchyAttributeDescriptors.forEach( (attributes) -> { + final Object collectedIdAttributes = attributes.getCollectedIdAttributes(); + assertThat( collectedIdAttributes ).isInstanceOf( AttributeMetadata.class ); + final AttributeMetadata idAttribute = (AttributeMetadata) collectedIdAttributes; + assertThat( idAttribute.getMember().getAnnotationUsage( Id.class ) ).isNotNull(); + assertThat( idAttribute.getMember().getAnnotationUsage( EmbeddedId.class ) ).isNull(); + + assertThat( attributes.getVersionAttribute() ).isNotNull(); + assertThat( attributes.getVersionAttribute().getMember().getAnnotationUsage( Version.class ) ).isNotNull(); + + assertThat( attributes.getTenantIdAttribute() ).isNotNull(); + assertThat( attributes.getTenantIdAttribute().getMember().getAnnotationUsage( TenantId.class ) ).isNotNull(); + } ); + } + + @Test + void testAggregatedId() { + final List hierarchyAttributeDescriptors = buildHierarchyAttributes( AggregatedIdEntity.class ); + + hierarchyAttributeDescriptors.forEach( (attributes) -> { + final Object collectedIdAttributes = attributes.getCollectedIdAttributes(); + assertThat( collectedIdAttributes ).isInstanceOf( AttributeMetadata.class ); + final AttributeMetadata idAttribute = (AttributeMetadata) collectedIdAttributes; + assertThat( idAttribute.getMember().getAnnotationUsage( Id.class ) ).isNull(); + assertThat( idAttribute.getMember().getAnnotationUsage( EmbeddedId.class ) ).isNotNull(); + + assertThat( attributes.getVersionAttribute() ).isNotNull(); + assertThat( attributes.getVersionAttribute().getMember().getAnnotationUsage( Version.class ) ).isNotNull(); + + assertThat( attributes.getTenantIdAttribute() ).isNotNull(); + assertThat( attributes.getTenantIdAttribute().getMember().getAnnotationUsage( TenantId.class ) ).isNotNull(); + } ); + } + + @Test + void testNonAggregatedId() { + final List hierarchyAttributeDescriptors = buildHierarchyAttributes( NonAggregatedIdEntity.class ); + + hierarchyAttributeDescriptors.forEach( (attributes) -> { + final Object collectedIdAttributes = attributes.getCollectedIdAttributes(); + assertThat( collectedIdAttributes ).isInstanceOf( List.class ); + //noinspection unchecked + final List idAttributes = (List) collectedIdAttributes; + idAttributes.forEach( (idAttribute) -> { + assertThat( idAttribute.getMember().getAnnotationUsage( Id.class ) ).isNotNull(); + assertThat( idAttribute.getMember().getAnnotationUsage( EmbeddedId.class ) ).isNull(); + } ); + + assertThat( attributes.getVersionAttribute() ).isNotNull(); + assertThat( attributes.getVersionAttribute().getMember().getAnnotationUsage( Version.class ) ).isNotNull(); + + assertThat( attributes.getTenantIdAttribute() ).isNotNull(); + assertThat( attributes.getTenantIdAttribute().getMember().getAnnotationUsage( TenantId.class ) ).isNotNull(); + } ); + } + + private static List buildHierarchyAttributes(Class... classes) { + final ManagedResources managedResources = new ManagedResourcesImpl.Builder() + .addLoadedClasses(classes) + .build(); + + final Index jandexIndex = SourceModelTestHelper.buildJandexIndex(classes); + final SourceModelBuildingContextImpl buildingContext = SourceModelTestHelper.createBuildingContext( + jandexIndex, + SIMPLE_CLASS_LOADING + ); + + final ProcessResult processResult = Processor.process( + managedResources, + null, + new Processor.Options() { }, + buildingContext + ); + + final OrmModelBuildingContextTesting ormModelBuildingContext = new OrmModelBuildingContextTesting( buildingContext ); + final List hierarchyAttributeDescriptors = HierarchyAttributeProcessor.preBindHierarchyAttributes( + processResult, + ormModelBuildingContext + ); + + // at this point, `hierarchyAttributeDescriptors` contains one entry per hierarchy in the same iteration order + final Set entityHierarchies = processResult.getEntityHierarchies(); + assertThat( hierarchyAttributeDescriptors.size() ).isEqualTo( entityHierarchies.size() ); + return hierarchyAttributeDescriptors; + } + +} diff --git a/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/NonAggregatedIdEntity.java b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/NonAggregatedIdEntity.java new file mode 100644 index 0000000..f828531 --- /dev/null +++ b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/NonAggregatedIdEntity.java @@ -0,0 +1,28 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.bind; + +import org.hibernate.annotations.RowId; +import org.hibernate.annotations.TenantId; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +/** + * @author Steve Ebersole + */ +@Entity +@Table(name = "non_agg_id_entities") +public class NonAggregatedIdEntity { + @Id private Integer id1; + @Id private Integer id2; + @Version private Integer version; + @TenantId private String tenantId; + +} diff --git a/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/SimpleIdEntity.java b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/SimpleIdEntity.java new file mode 100644 index 0000000..0278b31 --- /dev/null +++ b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/SimpleIdEntity.java @@ -0,0 +1,24 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.bind; + +import org.hibernate.annotations.TenantId; + +import jakarta.persistence.Id; +import jakarta.persistence.Version; + +/** + * @author Steve Ebersole + */ +public class SimpleIdEntity { + @Id + private Integer id; + @Version + private Integer version; + @TenantId + private String tenantId; +} diff --git a/hibernate-models-orm/src/test/java/org/hibernate/models/orm/util/OrmModelBuildingContextTesting.java b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/util/OrmModelBuildingContextTesting.java new file mode 100644 index 0000000..7674391 --- /dev/null +++ b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/util/OrmModelBuildingContextTesting.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.util; + +import org.hibernate.boot.internal.ClassmateContext; +import org.hibernate.models.orm.spi.OrmModelBuildingContext; +import org.hibernate.models.source.spi.AnnotationDescriptorRegistry; +import org.hibernate.models.source.spi.ClassDetailsRegistry; +import org.hibernate.models.source.spi.SourceModelBuildingContext; +import org.hibernate.models.spi.ClassLoading; + +import org.jboss.jandex.IndexView; + +import jakarta.persistence.SharedCacheMode; + +/** + * @author Steve Ebersole + */ +public class OrmModelBuildingContextTesting implements OrmModelBuildingContext { + private final ClassDetailsRegistry classDetailsRegistry; + private final AnnotationDescriptorRegistry annotationDescriptorRegistry; + private final ClassLoading classLoading; + private final IndexView jandexIndex; + private final ClassmateContext classmateContext; + private final SharedCacheMode sharedCacheMode; + + public OrmModelBuildingContextTesting(SourceModelBuildingContext sourceModelBuildingContext) { + this( sourceModelBuildingContext, null, SharedCacheMode.UNSPECIFIED ); + } + + public OrmModelBuildingContextTesting( + SourceModelBuildingContext sourceModelBuildingContext, + ClassmateContext classmateContext, + SharedCacheMode sharedCacheMode) { + classDetailsRegistry = sourceModelBuildingContext.getClassDetailsRegistry(); + annotationDescriptorRegistry = sourceModelBuildingContext.getAnnotationDescriptorRegistry(); + classLoading = sourceModelBuildingContext.getClassLoading(); + jandexIndex = sourceModelBuildingContext.getJandexIndex(); + this.classmateContext = classmateContext; + this.sharedCacheMode = sharedCacheMode; + } + + @Override + public ClassDetailsRegistry getClassDetailsRegistry() { + return classDetailsRegistry; + } + + @Override + public AnnotationDescriptorRegistry getAnnotationDescriptorRegistry() { + return annotationDescriptorRegistry; + } + + @Override + public ClassLoading getClassLoading() { + return classLoading; + } + + @Override + public IndexView getJandexIndex() { + return jandexIndex; + } + + @Override + public ClassmateContext getClassmateContext() { + return classmateContext; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return sharedCacheMode; + } +} diff --git a/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexFieldDetails.java b/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexFieldDetails.java index 86a7663..d3c878f 100644 --- a/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexFieldDetails.java +++ b/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexFieldDetails.java @@ -28,10 +28,7 @@ public JandexFieldDetails( SourceModelBuildingContext buildingContext) { super( buildingContext ); this.fieldInfo = fieldInfo; - this.type = buildingContext.getClassDetailsRegistry().resolveClassDetails( - fieldInfo.type().name().toString(), - JandexBuilders::buildClassDetailsStatic - ); + this.type = buildingContext.getClassDetailsRegistry().resolveClassDetails( fieldInfo.type().name().toString() ); } @Override diff --git a/hibernate-models-source/src/main/java/org/hibernate/models/source/spi/AnnotationUsage.java b/hibernate-models-source/src/main/java/org/hibernate/models/source/spi/AnnotationUsage.java index b2b4629..3723fa1 100644 --- a/hibernate-models-source/src/main/java/org/hibernate/models/source/spi/AnnotationUsage.java +++ b/hibernate-models-source/src/main/java/org/hibernate/models/source/spi/AnnotationUsage.java @@ -52,6 +52,18 @@ public interface AnnotationUsage { */ V getAttributeValue(String name); + /** + * The value of the named annotation attribute + */ + default V getAttributeValue(String name, V defaultValue) { + final Object attributeValue = getAttributeValue( name ); + if ( attributeValue == null ) { + return defaultValue; + } + //noinspection unchecked + return (V) attributeValue; + } + default V getAttributeValue(AttributeDescriptor attributeDescriptor) { return getAttributeValue( attributeDescriptor.getName() ); } @@ -60,42 +72,82 @@ default String getString(String name) { return getAttributeValue( name ); } + default String getString(String name, String defaultValue) { + return getAttributeValue( name, defaultValue ); + } + default Boolean getBoolean(String name) { return getAttributeValue( name ); } + default Boolean getBoolean(String name, boolean defaultValue) { + return getAttributeValue( name, defaultValue ); + } + default Byte getByte(String name) { return getAttributeValue( name ); } + default Byte getByte(String name, Byte defaultValue) { + return getAttributeValue( name, defaultValue ); + } + default Short getShort(String name) { return getAttributeValue( name ); } + default Short getShort(String name, Short defaultValue) { + return getAttributeValue( name, defaultValue ); + } + default Integer getInteger(String name) { return getAttributeValue( name ); } + default Integer getInteger(String name, Integer defaultValue) { + return getAttributeValue( name, defaultValue ); + } + default Long getLong(String name) { return getAttributeValue( name ); } + default Long getLong(String name, Long defaultValue) { + return getAttributeValue( name, defaultValue ); + } + default Float getFloat(String name) { return getAttributeValue( name ); } + default Float getFloat(String name, Float defaultValue) { + return getAttributeValue( name, defaultValue ); + } + default Double getDouble(String name) { return getAttributeValue( name ); } + default Double getDouble(String name, Double defaultValue) { + return getAttributeValue( name, defaultValue ); + } + default > E getEnum(String name) { return getAttributeValue( name ); } + default > E getEnum(String name, E defaultValue) { + return getAttributeValue( name, defaultValue ); + } + default ClassDetails getClassDetails(String name) { return getAttributeValue( name ); } + default ClassDetails getClassDetails(String name, ClassDetails defaultValue) { + return getAttributeValue( name, defaultValue ); + } + default AnnotationUsage getNestedUsage(String name) { return getAttributeValue( name ); }