diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/ModelBindingLogging.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/ModelBindingLogging.java new file mode 100644 index 0000000..c2c5c4d --- /dev/null +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/ModelBindingLogging.java @@ -0,0 +1,30 @@ +/* + * 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.models.Internal; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; +import org.jboss.logging.annotations.LogMessage; +import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.MessageLogger; +import org.jboss.logging.annotations.ValidIdRange; + +import static org.jboss.logging.Logger.Level.INFO; + +/** + * todo : find the proper min/max id range + * + * @author Steve Ebersole + */ +@Internal +public interface ModelBindingLogging extends BasicLogger { + String NAME = "org.hibernate.models.orm"; + + Logger MODEL_BINDING_LOGGER = Logger.getLogger( NAME ); +} diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingHelper.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/BindingHelper.java similarity index 71% rename from hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingHelper.java rename to hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/BindingHelper.java index 4ad9924..cc23cfc 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingHelper.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/BindingHelper.java @@ -4,16 +4,24 @@ * SPDX-License-Identifier: Apache-2.0 * Copyright: Red Hat Inc. and Hibernate Authors */ -package org.hibernate.models.orm.bind.spi; +package org.hibernate.models.orm.bind.internal; import java.lang.annotation.Annotation; +import java.util.Iterator; +import java.util.List; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.models.ModelsException; +import org.hibernate.models.orm.bind.spi.BindingContext; +import org.hibernate.models.orm.bind.spi.BindingOptions; +import org.hibernate.models.orm.bind.spi.QuotedIdentifierTarget; import org.hibernate.models.source.spi.AnnotationDescriptor; import org.hibernate.models.source.spi.AnnotationUsage; import org.hibernate.models.source.spi.AttributeDescriptor; +import static org.hibernate.models.orm.bind.ModelBindingLogging.MODEL_BINDING_LOGGER; + /** * @author Steve Ebersole */ @@ -98,4 +106,37 @@ public static T getValue(AnnotationUsage ann, String return ann.getAttributeValue( attributeName, defaultValue ); } + + public static void processSecondPassQueue(List secondPasses) { + if ( secondPasses == null ) { + return; + } + + int processedCount = 0; + final Iterator secondPassItr = secondPasses.iterator(); + while ( secondPassItr.hasNext() ) { + final SecondPass secondPass = secondPassItr.next(); + try { + final boolean success = secondPass.process(); + if ( success ) { + processedCount++; + secondPassItr.remove(); + } + } + catch (Exception e) { + MODEL_BINDING_LOGGER.debug( "Error processing second pass", e ); + } + } + + if ( !secondPasses.isEmpty() ) { + if ( processedCount == 0 ) { + // there are second-passes in the queue, but we were not able to + // successfully process any of them. this is a non-changing + // error condition - just throw an exception + throw new ModelsException( "Unable to process second-pass list" ); + } + + processSecondPassQueue( secondPasses ); + } + } } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/BindingStateImpl.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/BindingStateImpl.java index b11a197..754d534 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/BindingStateImpl.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/BindingStateImpl.java @@ -21,6 +21,7 @@ import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.models.ModelsException; import org.hibernate.models.internal.CollectionHelper; +import org.hibernate.models.orm.bind.internal.binders.EntityTypeBinder; import org.hibernate.models.orm.bind.internal.binders.IdentifiableTypeBinder; import org.hibernate.models.orm.bind.internal.binders.ManagedTypeBinder; import org.hibernate.models.orm.bind.internal.binders.TableBinder; @@ -88,14 +89,18 @@ public MetadataBuildingContext getMetadataBuildingContext() { public void registerTypeBinder(ManagedTypeMetadata type, ManagedTypeBinder binder) { typeBinders.put( type.getClassDetails(), binder ); -// if ( type instanceof IdentifiableTypeMetadata identifiableType ) { -// if ( identifiableType.getSuperType() != null ) { -// typeBindersBySuper.put( -// identifiableType.getSuperType().getClassDetails(), -// (IdentifiableTypeBinder) binder -// ); -// } -// } + if ( type instanceof IdentifiableTypeMetadata identifiableType ) { + if ( identifiableType.getSuperType() != null ) { + typeBindersBySuper.put( + identifiableType.getSuperType().getClassDetails(), + (IdentifiableTypeBinder) binder + ); + } + } + + if ( binder instanceof EntityTypeBinder ) { + metadataBuildingContext.getMetadataCollector().addEntityBinding( ( (EntityTypeBinder) binder ).getTypeBinding() ); + } } @Override @@ -108,6 +113,11 @@ public IdentifiableTypeBinder getSuperTypeBinder(ClassDetails type) { return typeBindersBySuper.get( type ); } + @Override + public void forEachType(NamedConsumer consumer) { + typeBinders.forEach( (classDetails, managedTypeBinder) -> consumer.consume( classDetails.getName(), managedTypeBinder ) ); + } + @Override public int getTableCount() { return tableMap.size(); diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/AttributeBinder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/AttributeBinder.java index 2fdec60..5606209 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/AttributeBinder.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/AttributeBinder.java @@ -6,19 +6,43 @@ */ package org.hibernate.models.orm.bind.internal.binders; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import org.hibernate.boot.model.internal.BasicValueBinder; +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.JavaType; +import org.hibernate.annotations.JdbcType; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.annotations.Mutability; +import org.hibernate.annotations.Nationalized; +import org.hibernate.annotations.OptimisticLock; +import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; +import org.hibernate.boot.model.naming.Identifier; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Property; +import org.hibernate.models.ModelsException; +import org.hibernate.models.orm.AnnotationPlacementException; +import org.hibernate.models.orm.bind.internal.BindingHelper; import org.hibernate.models.orm.bind.internal.SecondPass; import org.hibernate.models.orm.bind.spi.BindingContext; import org.hibernate.models.orm.bind.spi.BindingOptions; import org.hibernate.models.orm.bind.spi.BindingState; +import org.hibernate.models.orm.bind.spi.TableReference; import org.hibernate.models.orm.categorize.spi.AttributeMetadata; +import org.hibernate.models.source.spi.AnnotationUsage; +import org.hibernate.models.source.spi.ClassDetails; +import org.hibernate.models.source.spi.MemberDetails; +import org.hibernate.type.descriptor.java.BasicJavaType; +import org.hibernate.type.descriptor.java.MutabilityPlan; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Lob; + +import static jakarta.persistence.EnumType.ORDINAL; import static org.hibernate.models.orm.categorize.spi.AttributeMetadata.AttributeNature.BASIC; /** @@ -27,7 +51,7 @@ public class AttributeBinder { private final AttributeMetadata attributeMetadata; private final BindingState bindingState; - private final BindingOptions options; + private final BindingOptions bindingOptions; private final BindingContext bindingContext; private final Property binding; @@ -37,11 +61,11 @@ public class AttributeBinder { public AttributeBinder( AttributeMetadata attributeMetadata, BindingState bindingState, - BindingOptions options, + BindingOptions bindingOptions, BindingContext bindingContext) { this.attributeMetadata = attributeMetadata; this.bindingState = bindingState; - this.options = options; + this.bindingOptions = bindingOptions; this.bindingContext = bindingContext; this.binding = new Property(); @@ -50,13 +74,16 @@ public AttributeBinder( if ( attributeMetadata.getNature() == BASIC ) { final var basicValue = createBasicValue(); binding.setValue( basicValue ); - registerValueSecondPass( new BasicValueSecondPass( attributeMetadata, basicValue ) ); } else { throw new UnsupportedOperationException( "Not yet implemented" ); } } + public Property getBinding() { + return binding; + } + private void registerValueSecondPass(ValueSecondPass secondPass) { if ( valueSecondPasses == null ) { valueSecondPasses = new ArrayList<>(); @@ -64,16 +91,190 @@ private void registerValueSecondPass(ValueSecondPass secondPass) { valueSecondPasses.add( secondPass ); } + public void processSecondPasses() { + BindingHelper.processSecondPassQueue( valueSecondPasses ); + } + private BasicValue createBasicValue() { - return new BasicValue( bindingState.getMetadataBuildingContext() ); + final BasicValue basicValue = new BasicValue( bindingState.getMetadataBuildingContext() ); + // probably we don't need this as a second pass... + registerValueSecondPass( new BasicValueSecondPass( attributeMetadata, getBinding(), basicValue, bindingState, bindingContext ) ); + + return basicValue; } - public Property getBinding() { - return binding; + private static void processColumn( + MemberDetails member, + Property property, + BasicValue basicValue, + BindingState bindingState, + BindingContext bindingContext) { + // todo : implicit column + final var columnAnn = member.getAnnotationUsage( Column.class ); + final var column = ColumnBinder.bindColumn( columnAnn, property::getName ); + + if ( columnAnn != null ) { + final var tableName = columnAnn.getString( "table", null ); + TableReference tableByName = null; + if ( tableName != null ) { + final Identifier identifier = Identifier.toIdentifier( tableName ); + tableByName = bindingState.getTableByName( identifier.getCanonicalName() ); + basicValue.setTable( tableByName.getBinding() ); + } + } + + basicValue.addColumn( column ); } + private static void processLob(MemberDetails member, BasicValue basicValue) { + if ( member.getAnnotationUsage( Lob.class ) != null ) { + basicValue.makeLob(); + } + } + + private static void processNationalized(MemberDetails member, BasicValue basicValue) { + if ( member.getAnnotationUsage( Nationalized.class ) != null ) { + basicValue.makeNationalized(); + } + } + + private static void processEnumerated(MemberDetails member, BasicValue basicValue) { + final AnnotationUsage enumerated = member.getAnnotationUsage( Enumerated.class ); + if ( enumerated == null ) { + return; + } + + basicValue.setEnumerationStyle( enumerated.getEnum( "value", ORDINAL ) ); + } + + private static void processConversion( + MemberDetails member, + BasicValue basicValue, + BindingContext bindingContext) { + // todo : do we need to account for auto-applied converters here? + final var convertAnn = member.getAnnotationUsage( Convert.class ); + if ( convertAnn == null ) { + return; + } + + if ( convertAnn.getBoolean( "disableConversion" ) ) { + return; + } + + if ( convertAnn.getString( "attributeName" ) != null ) { + throw new ModelsException( "@Convert#attributeName should not be specified on basic mappings - " + member.getName() ); + } + + final ClassDetails converterClassDetails = convertAnn.getClassDetails( "converter" ); + final Class> javaClass = converterClassDetails.toJavaClass(); + basicValue.setJpaAttributeConverterDescriptor( new ClassBasedConverterDescriptor( + javaClass, + bindingContext.getClassmateContext() + ) ); + } + + private static void processJavaType(MemberDetails member, BasicValue basicValue) { + // todo : do we need to account for JavaTypeRegistration here? + final var javaTypeAnn = member.getAnnotationUsage( JavaType.class ); + if ( javaTypeAnn == null ) { + return; + } + + basicValue.setExplicitJavaTypeAccess( (typeConfiguration) -> { + final var classDetails = javaTypeAnn.getClassDetails( "value" ); + final Class> javaClass = classDetails.toJavaClass(); + try { + return javaClass.getConstructor().newInstance(); + } + catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + final ModelsException modelsException = new ModelsException( "Error instantiating local @JavaType - " + member.getName() ); + modelsException.addSuppressed( e ); + throw modelsException; + } + } ); + } + + private static void processJdbcType(MemberDetails member, BasicValue basicValue) { + // todo : do we need to account for JdbcTypeRegistration here? + final var jdbcTypeAnn = member.getAnnotationUsage( JdbcType.class ); + final var jdbcTypeCodeAnn = member.getAnnotationUsage( JdbcTypeCode.class ); + + if ( jdbcTypeAnn != null ) { + if ( jdbcTypeCodeAnn != null ) { + throw new AnnotationPlacementException( + "Illegal combination of @JdbcType and @JdbcTypeCode - " + member.getName() + ); + } + + basicValue.setExplicitJdbcTypeAccess( (typeConfiguration) -> { + final var classDetails = jdbcTypeAnn.getClassDetails( "value" ); + final Class javaClass = classDetails.toJavaClass(); + try { + return javaClass.getConstructor().newInstance(); + } + catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + final ModelsException modelsException = new ModelsException( "Error instantiating local @JdbcType - " + member.getName() ); + modelsException.addSuppressed( e ); + throw modelsException; + } + } ); + } + else if ( jdbcTypeCodeAnn != null ) { + final Integer typeCode = jdbcTypeCodeAnn.getInteger( "value" ); + basicValue.setExplicitJdbcTypeCode( typeCode ); + } + } + + private static void processMutability( + MemberDetails member, + Property property, BasicValue basicValue) { + final var mutabilityAnn = member.getAnnotationUsage( Mutability.class ); + final var immutableAnn = member.getAnnotationUsage( Immutable.class ); + + if ( immutableAnn != null ) { + if ( mutabilityAnn != null ) { + throw new AnnotationPlacementException( + "Illegal combination of @Mutability and @Immutable - " + member.getName() + ); + } + + property.setUpdateable( false ); + } + else if ( mutabilityAnn != null ) { + basicValue.setExplicitMutabilityPlanAccess( (typeConfiguration) -> { + final ClassDetails classDetails = mutabilityAnn.getClassDetails( "value" ); + final Class> javaClass = classDetails.toJavaClass(); + try { + return javaClass.getConstructor().newInstance(); + } + catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + final ModelsException modelsException = new ModelsException( "Error instantiating local @MutabilityPlan - " + member.getName() ); + modelsException.addSuppressed( e ); + throw modelsException; + } + } ); + } + } + + private static void processOptimisticLocking( + MemberDetails member, + Property property, + BasicValue basicValue) { + final var annotationUsage = member.getAnnotationUsage( OptimisticLock.class ); + if ( annotationUsage != null ) { + if ( annotationUsage.getBoolean( "excluded" ) ) { + property.setOptimisticLocked( false ); + return; + } + } + + property.setOptimisticLocked( true ); + } + + @FunctionalInterface interface ValueSecondPass extends SecondPass { + boolean processValue(); @Override @@ -82,11 +283,27 @@ default boolean process() { } } - private record BasicValueSecondPass(AttributeMetadata attributeMetadata, BasicValue binding) implements ValueSecondPass { + private record BasicValueSecondPass( + AttributeMetadata attributeMetadata, + Property property, + BasicValue basicValue, + BindingState bindingState, + BindingContext bindingContext) implements ValueSecondPass { @Override public boolean processValue() { - return false; + final MemberDetails member = attributeMetadata.getMember(); + processColumn( member, property, basicValue, bindingState, bindingContext ); + processLob( member, basicValue ); + processNationalized( member, basicValue ); + processEnumerated( member, basicValue ); + processConversion( member, basicValue, bindingContext ); + processJavaType( member, basicValue ); + processJdbcType( member, basicValue ); + processMutability( member, property, basicValue ); + processOptimisticLocking( member, property, basicValue ); + + return true; } } } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/ColumnBinder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/ColumnBinder.java index 3c267c3..9fac7be 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/ColumnBinder.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/ColumnBinder.java @@ -9,7 +9,7 @@ import java.util.function.Supplier; import org.hibernate.mapping.Column; -import org.hibernate.models.orm.categorize.spi.ModelCategorizationContext; +import org.hibernate.models.orm.bind.internal.BindingHelper; import org.hibernate.models.source.spi.AnnotationUsage; import static org.hibernate.internal.util.NullnessHelper.nullif; @@ -20,8 +20,7 @@ public class ColumnBinder { public static Column bindColumn( AnnotationUsage annotationUsage, - Supplier defaultNameSupplier, - ModelCategorizationContext modelBuildingContext) { + Supplier defaultNameSupplier) { return bindColumn( annotationUsage, defaultNameSupplier, @@ -29,8 +28,7 @@ public static Column bindColumn( true, 255, 0, - 0, - modelBuildingContext + 0 ); } @@ -39,27 +37,29 @@ public static Column bindColumn( Supplier defaultNameSupplier, boolean uniqueByDefault, boolean nullableByDefault, - long lengthByDefault, + int lengthByDefault, int precisionByDefault, - int scaleByDefault, - ModelCategorizationContext modelBuildingContext) { + int scaleByDefault) { final Column result = new Column(); - result.setName( columnName( annotationUsage, defaultNameSupplier, modelBuildingContext ) ); + result.setName( columnName( annotationUsage, defaultNameSupplier ) ); - 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 ) ); + result.setUnique( BindingHelper.getValue( annotationUsage, "unique", uniqueByDefault ) ); + result.setNullable( BindingHelper.getValue( annotationUsage, "nullable", nullableByDefault ) ); + result.setSqlType( BindingHelper.getValue( annotationUsage, "columnDefinition", null ) ); + result.setLength( BindingHelper.getValue( annotationUsage, "length", lengthByDefault ) ); + result.setPrecision( BindingHelper.getValue( annotationUsage, "precision", precisionByDefault ) ); + result.setScale( BindingHelper.getValue( annotationUsage, "scale", scaleByDefault ) ); return result; } private static String columnName( AnnotationUsage columnAnnotation, - Supplier defaultNameSupplier, - ModelCategorizationContext modelBuildingContext) { + Supplier defaultNameSupplier) { + if ( columnAnnotation == null ) { + return defaultNameSupplier.get(); + } + return nullif( columnAnnotation.getAttributeValue( "name" ), defaultNameSupplier ); } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/EntityTypeBinder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/EntityTypeBinder.java index 987ff83..9e61b3b 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/EntityTypeBinder.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/EntityTypeBinder.java @@ -6,10 +6,21 @@ */ package org.hibernate.models.orm.bind.internal.binders; -import java.util.HashMap; import java.util.List; import java.util.Map; +import org.hibernate.annotations.OptimisticLockType; +import org.hibernate.annotations.OptimisticLocking; +import org.hibernate.annotations.SoftDelete; +import org.hibernate.annotations.SoftDeleteType; +import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.naming.PhysicalNamingStrategy; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.engine.OptimisticLockStyle; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Column; import org.hibernate.mapping.IdentifiableTypeClass; import org.hibernate.mapping.Join; import org.hibernate.mapping.JoinedSubclass; @@ -17,20 +28,28 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.RootClass; import org.hibernate.mapping.SingleTableSubclass; +import org.hibernate.mapping.SoftDeletable; +import org.hibernate.mapping.Table; import org.hibernate.mapping.TableOwner; import org.hibernate.mapping.UnionSubclass; import org.hibernate.models.ModelsException; import org.hibernate.models.orm.bind.internal.SecondaryTable; import org.hibernate.models.orm.bind.spi.BindingContext; +import org.hibernate.models.orm.bind.internal.BindingHelper; import org.hibernate.models.orm.bind.spi.BindingOptions; import org.hibernate.models.orm.bind.spi.BindingState; import org.hibernate.models.orm.bind.spi.TableReference; import org.hibernate.models.orm.categorize.spi.EntityHierarchy; import org.hibernate.models.orm.categorize.spi.EntityTypeMetadata; import org.hibernate.models.orm.categorize.spi.IdentifiableTypeMetadata; +import org.hibernate.models.source.spi.AnnotationUsage; +import org.hibernate.models.source.spi.ClassDetails; +import jakarta.persistence.Entity; import jakarta.persistence.InheritanceType; +import static org.hibernate.internal.util.StringHelper.coalesce; + /** * Binder for binding a {@linkplain PersistentClass} from a {@linkplain EntityTypeMetadata} * @@ -40,6 +59,7 @@ public class EntityTypeBinder extends IdentifiableTypeBinder { private final PersistentClass binding; private final TableReference primaryTable; + private final DelegateBinders delegateBinders; private Map secondaryTableMap; public EntityTypeBinder( @@ -49,13 +69,44 @@ public EntityTypeBinder( DelegateBinders delegateBinders, BindingState state, BindingOptions options, - BindingContext bindingContext) { - super( type, superType, hierarchyRelation, state, options, bindingContext ); + BindingContext context) { + super( type, superType, hierarchyRelation, state, options, context ); + this.delegateBinders = delegateBinders; + final ClassDetails classDetails = type.getClassDetails(); this.binding = createBinding(); + final AnnotationUsage entityAnn = classDetails.getAnnotationUsage( Entity.class ); + final String jpaEntityName = BindingHelper.getValue( entityAnn, "name", null ); + final String entityName; + final String importName; + + if ( classDetails.getName() != null + && !classDetails.getName().equals( classDetails.getClassName() ) ) { + // should indicate a dynamic model + entityName = classDetails.getName(); + } + else { + entityName = classDetails.getClassName(); + } + + if ( jpaEntityName != null ) { + importName = jpaEntityName; + } + else { + importName = StringHelper.unqualifyEntityName( entityName ); + } + + binding.setClassName( classDetails.getClassName() ); + binding.setEntityName( entityName ); + binding.setJpaEntityName( jpaEntityName ); + + state.getMetadataBuildingContext().getMetadataCollector().addImport( importName, entityName ); + + this.primaryTable = delegateBinders.getTableBinder().processPrimaryTable( getManagedType() ); - ( (TableOwner) binding ).setTable( primaryTable.getBinding() ); + final var table = primaryTable.getBinding(); + ( (TableOwner) binding ).setTable( table ); final List secondaryTables = delegateBinders.getTableBinder().processSecondaryTables( getManagedType() ); secondaryTables.forEach( (secondaryTable) -> { @@ -66,6 +117,14 @@ public EntityTypeBinder( join.setInverse( !secondaryTable.owned() ); } ); + if ( binding instanceof RootClass ) { + processSoftDelete( primaryTable.getBinding(), classDetails, state, context ); + processOptimisticLocking( primaryTable.getBinding(), classDetails, state, context ); + processCacheRegions( classDetails, state, context ); + } + + processCaching( classDetails, state, context ); + prepareBinding( delegateBinders ); } @@ -74,11 +133,21 @@ public PersistentClass getTypeBinding() { return binding; } + @Override + public Table getTable() { + return binding.getTable(); + } + @Override public EntityTypeMetadata getManagedType() { return (EntityTypeMetadata) super.getManagedType(); } + @Override + public EntityTypeMetadata findSuperEntity() { + return getManagedType(); + } + private PersistentClass createBinding() { if ( getHierarchyRelation() == EntityHierarchy.HierarchyRelation.SUB ) { return createSubclass(); @@ -180,4 +249,110 @@ protected void prepareBinding(DelegateBinders delegateBinders) { super.prepareBinding( delegateBinders ); } + + private void processSoftDelete( + Table primaryTable, + ClassDetails classDetails, + BindingState state, + BindingContext context) { + final AnnotationUsage softDeleteConfig = getTypeBinding() instanceof RootClass + ? classDetails.getAnnotationUsage( SoftDelete.class ) + : null; + if ( softDeleteConfig == null ) { + return; + } + + final BasicValue softDeleteIndicatorValue = createSoftDeleteIndicatorValue( softDeleteConfig, primaryTable, state, context ); + final Column softDeleteIndicatorColumn = createSoftDeleteIndicatorColumn( + softDeleteConfig, + softDeleteIndicatorValue, + state, + context + ); + primaryTable.addColumn( softDeleteIndicatorColumn ); + ( (SoftDeletable) primaryTable ).enableSoftDelete( softDeleteIndicatorColumn ); + } + + private static BasicValue createSoftDeleteIndicatorValue( + AnnotationUsage softDeleteAnn, + Table table, + BindingState state, + BindingContext context) { + assert softDeleteAnn != null; + + final var converterClassDetails = softDeleteAnn.getClassDetails( "converter" ); + final ClassBasedConverterDescriptor converterDescriptor = new ClassBasedConverterDescriptor( + converterClassDetails.toJavaClass(), + context.getBootstrapContext().getClassmateContext() + ); + + final BasicValue softDeleteIndicatorValue = new BasicValue( state.getMetadataBuildingContext(), table ); + softDeleteIndicatorValue.makeSoftDelete( softDeleteAnn.getEnum( "strategy" ) ); + softDeleteIndicatorValue.setJpaAttributeConverterDescriptor( converterDescriptor ); + softDeleteIndicatorValue.setImplicitJavaTypeAccess( (typeConfiguration) -> converterDescriptor.getRelationalValueResolvedType().getErasedType() ); + return softDeleteIndicatorValue; + } + + private static Column createSoftDeleteIndicatorColumn( + AnnotationUsage softDeleteConfig, + BasicValue softDeleteIndicatorValue, + BindingState state, + BindingContext context) { + final Column softDeleteColumn = new Column(); + + applyColumnName( softDeleteColumn, softDeleteConfig, state, context ); + + softDeleteColumn.setLength( 1 ); + softDeleteColumn.setNullable( false ); + softDeleteColumn.setUnique( false ); + softDeleteColumn.setComment( "Soft-delete indicator" ); + + softDeleteColumn.setValue( softDeleteIndicatorValue ); + softDeleteIndicatorValue.addColumn( softDeleteColumn ); + + return softDeleteColumn; + } + + private static void applyColumnName( + Column softDeleteColumn, + AnnotationUsage softDeleteConfig, + BindingState state, + BindingContext context) { + final Database database = state.getMetadataBuildingContext().getMetadataCollector().getDatabase(); + final PhysicalNamingStrategy namingStrategy = state.getMetadataBuildingContext().getBuildingOptions().getPhysicalNamingStrategy(); + final SoftDeleteType strategy = softDeleteConfig.getEnum( "strategy" ); + final String logicalColumnName = coalesce( + strategy.getDefaultColumnName(), + softDeleteConfig.getString( softDeleteConfig.getString( "columnName" ) ) + ); + final Identifier physicalColumnName = namingStrategy.toPhysicalColumnName( + database.toIdentifier( logicalColumnName ), + database.getJdbcEnvironment() + ); + softDeleteColumn.setName( physicalColumnName.render( database.getDialect() ) ); + } + + private void processOptimisticLocking(Table binding, ClassDetails classDetails, BindingState state, BindingContext context) { + final var optimisticLocking = classDetails.getAnnotationUsage( OptimisticLocking.class ); + + if ( optimisticLocking != null ) { + final var optimisticLockingType = optimisticLocking.getEnum( "type", OptimisticLockType.VERSION ); + final var rootEntity = (RootClass) getTypeBinding(); + rootEntity.setOptimisticLockStyle( OptimisticLockStyle.valueOf( optimisticLockingType.name() ) ); + } + } + + private void processCacheRegions(ClassDetails classDetails, BindingState state, BindingContext context) { + + } + + private void processCaching(ClassDetails classDetails, BindingState state, BindingContext context) { + + } + + @Override + public void processSecondPasses() { + delegateBinders.getTableBinder().processSecondPasses(); + super.processSecondPasses(); + } } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/IdentifiableTypeBinder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/IdentifiableTypeBinder.java index 5edd7c6..f064d64 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/IdentifiableTypeBinder.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/IdentifiableTypeBinder.java @@ -8,13 +8,32 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.OptimisticLock; +import org.hibernate.annotations.OptimisticLockType; +import org.hibernate.annotations.OptimisticLocking; +import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.IdentifiableTypeClass; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Table; +import org.hibernate.mapping.Value; +import org.hibernate.models.orm.AnnotationPlacementException; import org.hibernate.models.orm.bind.spi.BindingContext; import org.hibernate.models.orm.bind.spi.BindingOptions; import org.hibernate.models.orm.bind.spi.BindingState; +import org.hibernate.models.orm.categorize.spi.AttributeMetadata; import org.hibernate.models.orm.categorize.spi.EntityHierarchy; +import org.hibernate.models.orm.categorize.spi.EntityTypeMetadata; import org.hibernate.models.orm.categorize.spi.IdentifiableTypeMetadata; +import org.hibernate.models.orm.categorize.spi.KeyMapping; +import org.hibernate.models.source.spi.ClassDetails; + +import jakarta.persistence.Version; + +import static org.hibernate.models.orm.categorize.xml.internal.AttributeProcessor.processNaturalId; /** * @author Steve Ebersole @@ -24,6 +43,7 @@ public abstract class IdentifiableTypeBinder extends ManagedTypeBinder { private final EntityHierarchy.HierarchyRelation hierarchyRelation; private final List attributeBinders; + private final IdentifiableTypeBinder superTypeBinder; public IdentifiableTypeBinder( IdentifiableTypeMetadata type, @@ -35,10 +55,28 @@ public IdentifiableTypeBinder( super( type, state, options, bindingContext ); this.superType = superType; this.hierarchyRelation = hierarchyRelation; - + this.superTypeBinder = state.getSuperTypeBinder( getManagedType().getClassDetails() ); this.attributeBinders = new ArrayList<>( type.getNumberOfAttributes() ); } + public abstract EntityTypeMetadata findSuperEntity(); + + public EntityTypeBinder getSuperEntityBinder() { + IdentifiableTypeBinder check = superTypeBinder; + do { + if ( check.getTypeBinding() instanceof PersistentClass ) { + return (EntityTypeBinder) check.getSuperTypeBinder(); + } + check = check.getSuperTypeBinder(); + } while ( check != null ); + + return null; + } + + public IdentifiableTypeBinder getSuperTypeBinder() { + return superTypeBinder; + } + public abstract IdentifiableTypeClass getTypeBinding(); public IdentifiableTypeMetadata getSuperType() { @@ -49,6 +87,8 @@ public EntityHierarchy.HierarchyRelation getHierarchyRelation() { return hierarchyRelation; } + public abstract Table getTable(); + @Override public IdentifiableTypeMetadata getManagedType() { return (IdentifiableTypeMetadata) super.getManagedType(); @@ -56,17 +96,59 @@ public IdentifiableTypeMetadata getManagedType() { @Override protected void prepareBinding(DelegateBinders delegateBinders) { -// getManagedType().forEachAttribute( (index, attributeMetadata) -> { -// final AttributeBinder attributeBinder = new AttributeBinder( -// attributeMetadata, -// getBindingState(), -// getOptions(), -// getBindingContext() -// ); -// attributeBinders.add( attributeBinder ); -// getTypeBinding().applyProperty( attributeBinder.getBinding() ); -// } ); + final var table = getTable(); + final var managedType = getManagedType(); + + managedType.forEachAttribute( (index, attributeMetadata) -> { + final var attributeBinder = new AttributeBinder( + attributeMetadata, + getBindingState(), + getOptions(), + getBindingContext() + ); + + final var property = attributeBinder.getBinding(); + + final var value = property.getValue(); + applyTable( value, table ); + + processIdMapping( attributeMetadata, property ); + processNaturalId( attributeMetadata, property ); + + attributeBinders.add( attributeBinder ); + getTypeBinding().applyProperty( property ); + } ); super.prepareBinding( delegateBinders ); } + + private void processIdMapping(AttributeMetadata attributeMetadata, Property property) { + final KeyMapping idMapping = getManagedType().getHierarchy().getIdMapping(); + if ( !idMapping.contains( attributeMetadata ) ) { + return; + } + + // todo : do it + } + + private void processNaturalId(AttributeMetadata attributeMetadata, Property property) { + final var naturalIdAnn = attributeMetadata.getMember().getAnnotationUsage( NaturalId.class ); + if ( naturalIdAnn == null ) { + return; + } + property.setNaturalIdentifier( true ); + property.setUpdateable( naturalIdAnn.getBoolean( "mutable" ) ); + } + + private void applyTable(Value value, Table table) { + if ( value instanceof BasicValue ) { + ( (BasicValue) value ).setTable( table ); + } + } + + @Override + public void processSecondPasses() { + attributeBinders.forEach( AttributeBinder::processSecondPasses ); + } + } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/ManagedTypeBinder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/ManagedTypeBinder.java index 90e5af6..27b0cd6 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/ManagedTypeBinder.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/ManagedTypeBinder.java @@ -52,4 +52,8 @@ protected void prepareBinding(DelegateBinders delegateBinders) { // todo : ideally we'd pre-process attributes here, but the boot mapping model has // no commonality between embeddable and identifiable (no ManagedType corollary) } + + public void processSecondPasses() { + + } } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/MappedSuperTypeBinder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/MappedSuperTypeBinder.java index 744f8a9..84f6d8b 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/MappedSuperTypeBinder.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/MappedSuperTypeBinder.java @@ -6,12 +6,13 @@ */ package org.hibernate.models.orm.bind.internal.binders; -import org.hibernate.mapping.IdentifiableTypeClass; import org.hibernate.mapping.MappedSuperclass; +import org.hibernate.mapping.Table; import org.hibernate.models.orm.bind.spi.BindingContext; import org.hibernate.models.orm.bind.spi.BindingOptions; import org.hibernate.models.orm.bind.spi.BindingState; import org.hibernate.models.orm.categorize.spi.EntityHierarchy; +import org.hibernate.models.orm.categorize.spi.EntityTypeMetadata; import org.hibernate.models.orm.categorize.spi.IdentifiableTypeMetadata; import org.hibernate.models.orm.categorize.spi.MappedSuperclassTypeMetadata; @@ -35,7 +36,26 @@ public MappedSuperTypeBinder( } @Override - public IdentifiableTypeClass getTypeBinding() { + public MappedSuperclass getTypeBinding() { + return binding; + } + + @Override + public Table getTable() { + final var superEntityBinder = getSuperEntityBinder(); + if ( superEntityBinder == null ) { + return null; + } + + return superEntityBinder.getTypeBinding().getTable(); + } + + @Override + public EntityTypeMetadata findSuperEntity() { + if ( getSuperType() != null ) { + final var superTypeBinder = getBindingState().getSuperTypeBinder( getManagedType().getClassDetails() ); + return superTypeBinder.findSuperEntity(); + } return null; } } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/TableBinder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/TableBinder.java index c05b96b..f0beaf7 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/TableBinder.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/internal/binders/TableBinder.java @@ -8,7 +8,6 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import org.hibernate.annotations.Comment; @@ -22,14 +21,13 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.mapping.Table; -import org.hibernate.models.ModelsException; import org.hibernate.models.internal.StringHelper; import org.hibernate.models.orm.AnnotationPlacementException; import org.hibernate.models.orm.bind.internal.InLineView; import org.hibernate.models.orm.bind.internal.PhysicalTable; import org.hibernate.models.orm.bind.internal.SecondPass; import org.hibernate.models.orm.bind.spi.BindingContext; -import org.hibernate.models.orm.bind.spi.BindingHelper; +import org.hibernate.models.orm.bind.internal.BindingHelper; import org.hibernate.models.orm.bind.spi.BindingOptions; import org.hibernate.models.orm.bind.spi.BindingState; import org.hibernate.models.orm.bind.spi.PhysicalTableReference; @@ -361,36 +359,8 @@ default boolean process() { } } - public void processQueue() { - if ( secondPasses == null ) { - return; - } - - int processedCount = 0; - final Iterator secondPassItr = secondPasses.iterator(); - while ( secondPassItr.hasNext() ) { - final SecondPass secondPass = secondPassItr.next(); - try { - final boolean success = secondPass.process(); - if ( success ) { - processedCount++; - secondPassItr.remove(); - } - } - catch (Exception ignoreForNow) { - } - } - - if ( !secondPasses.isEmpty() ) { - if ( processedCount == 0 ) { - // there are second-passes in the queue, but we were not able to - // successfully process any of them. this is a non-changing - // error condition - just throw an exception - throw new ModelsException( "Unable to process second-pass list" ); - } - - processQueue(); - } + public void processSecondPasses() { + BindingHelper.processSecondPassQueue( secondPasses ); } private void applyComment(Table table, AnnotationUsage tableAnn, AnnotationUsage commentAnn) { diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingCoordinator.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingCoordinator.java index bc02ce2..6901696 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingCoordinator.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingCoordinator.java @@ -97,7 +97,8 @@ public static void coordinateBinding( categorizedDomainModel.forEachEntityHierarchy( bindingCoordinator::processHierarchy ); // complete tables - bindingCoordinator.delegateBinders.getTableBinder().processQueue(); + bindingCoordinator.delegateBinders.getTableBinder().processSecondPasses(); + bindingCoordinator.delegateBinders.getTableBinder().processSecondPasses(); // process identifiers categorizedDomainModel.forEachEntityHierarchy( (index, hierarchy) -> { @@ -106,6 +107,9 @@ public static void coordinateBinding( } ); + state.forEachType( (name, managedTypeBinder) -> { + managedTypeBinder.processSecondPasses(); + } ); } private void processHierarchy(int index, EntityHierarchy hierarchy) { diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingState.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingState.java index 871034a..473839f 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingState.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/bind/spi/BindingState.java @@ -46,5 +46,6 @@ default ManagedTypeBinder getTypeBinder(ManagedTypeMetadata type) { ManagedTypeBinder getTypeBinder(ClassDetails type); IdentifiableTypeBinder getSuperTypeBinder(ClassDetails type); + void forEachType(NamedConsumer consumer); } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/internal/NonAggregatedKeyMappingImpl.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/internal/NonAggregatedKeyMappingImpl.java index e77d5f0..72b6dc5 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/internal/NonAggregatedKeyMappingImpl.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/internal/NonAggregatedKeyMappingImpl.java @@ -46,4 +46,14 @@ public void forEachAttribute(AttributeConsumer consumer) { consumer.accept( i, idAttributes.get( i ) ); } } + + @Override + public boolean contains(AttributeMetadata attributeMetadata) { + for ( int i = 0; i < idAttributes.size(); i++ ) { + if ( idAttributes.get( i ) == attributeMetadata ) { + return true; + } + } + return false; + } } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/KeyMapping.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/KeyMapping.java index 25624ee..ce4b176 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/KeyMapping.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/KeyMapping.java @@ -15,4 +15,6 @@ public interface KeyMapping { ClassDetails getKeyType(); void forEachAttribute(AttributeConsumer consumer); + + boolean contains(AttributeMetadata attributeMetadata); } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/ManagedTypeMetadata.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/ManagedTypeMetadata.java index c1928da..67c2583 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/ManagedTypeMetadata.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/ManagedTypeMetadata.java @@ -19,6 +19,7 @@ * @author Steve Ebersole */ public interface ManagedTypeMetadata { + enum Kind { ENTITY, MAPPED_SUPER, EMBEDDABLE } Kind getManagedTypeKind(); diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/SingleAttributeKeyMapping.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/SingleAttributeKeyMapping.java index b59eeeb..05ac10b 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/SingleAttributeKeyMapping.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/categorize/spi/SingleAttributeKeyMapping.java @@ -26,4 +26,9 @@ default ClassDetails getKeyType() { default void forEachAttribute(AttributeConsumer consumer) { consumer.accept( 0, getAttribute() ); } + + @Override + default boolean contains(AttributeMetadata attributeMetadata) { + return attributeMetadata == getAttribute(); + } } diff --git a/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/SimpleBindingCoordinatorTests.java b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/SimpleBindingCoordinatorTests.java index ea48f45..05fecbb 100644 --- a/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/SimpleBindingCoordinatorTests.java +++ b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/bind/SimpleBindingCoordinatorTests.java @@ -15,6 +15,11 @@ import org.hibernate.boot.model.process.spi.MetadataBuildingProcess; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; import org.hibernate.models.orm.bind.internal.BindingContextImpl; import org.hibernate.models.orm.bind.internal.BindingOptionsImpl; import org.hibernate.models.orm.bind.internal.BindingStateImpl; @@ -81,6 +86,19 @@ void testIt(ServiceRegistryScope scope) { assertThat( namespaceItr.hasNext() ).isFalse(); assertThat( namespace1.getTables() ).hasSize( 1 ); assertThat( namespace2.getTables() ).hasSize( 1 ); + + final RootClass entityBinding = (RootClass) context.getMetadataCollector().getEntityBinding( SimpleEntity.class.getName() ); + final Property id = entityBinding.getProperty( "id" ); + assertThat( id.getValue().getTable().getName() ).isEqualTo( "SIMPLETONS" ); + assertThat( ( (Column) ( (BasicValue) id.getValue() ).getColumn() ).getCanonicalName() ).isEqualTo( "id" ); + + final Property name = entityBinding.getProperty( "name" ); + assertThat( id.getValue().getTable().getName() ).isEqualTo( "SIMPLETONS" ); + assertThat( ( (Column) ( (BasicValue) name.getValue() ).getColumn() ).getCanonicalName() ).isEqualTo( "name" ); + + final Property data = entityBinding.getProperty( "data" ); + assertThat( data.getValue().getTable().getName() ).isEqualTo( "SIMPLE_STUFF" ); + assertThat( ( (Column) ( (BasicValue) data.getValue() ).getColumn() ).getCanonicalName() ).isEqualTo( "datum" ); }, scope.getRegistry(), SimpleEntity.class