Skip to content

Commit

Permalink
#80 - Union subclass binding
Browse files Browse the repository at this point in the history
  • Loading branch information
sebersole committed Nov 20, 2023
1 parent aa87600 commit 0e6c653
Show file tree
Hide file tree
Showing 12 changed files with 568 additions and 215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
public record UnionTable(
Identifier logicalName,
UnionTable base,
TableReference base,
DenormalizedTable binding,
boolean exportable) implements TableReference {
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,35 @@
import java.util.List;

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.annotations.TimeZoneColumn;
import org.hibernate.annotations.TimeZoneStorage;
import org.hibernate.annotations.TimeZoneStorageType;
import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.models.AnnotationPlacementException;
import org.hibernate.boot.models.bind.internal.BindingHelper;
import org.hibernate.boot.models.bind.internal.SecondPass;
import org.hibernate.boot.models.bind.spi.BindingContext;
import org.hibernate.boot.models.bind.spi.BindingOptions;
import org.hibernate.boot.models.bind.spi.BindingState;
import org.hibernate.boot.models.bind.spi.TableReference;
import org.hibernate.boot.models.categorize.spi.AttributeMetadata;
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Property;
import org.hibernate.models.ModelsException;
import org.hibernate.boot.models.AnnotationPlacementException;
import org.hibernate.boot.models.bind.internal.SecondPass;
import org.hibernate.boot.models.categorize.spi.AttributeMetadata;
import org.hibernate.models.spi.AnnotationUsage;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.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 jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;

import static jakarta.persistence.EnumType.ORDINAL;
import static org.hibernate.annotations.TimeZoneStorageType.AUTO;
import static org.hibernate.annotations.TimeZoneStorageType.COLUMN;
import static org.hibernate.boot.models.categorize.spi.AttributeMetadata.AttributeNature.BASIC;
Expand Down Expand Up @@ -105,136 +97,46 @@ public void processSecondPasses() {
private BasicValue createBasicValue() {
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 ) );
registerValueSecondPass( new BasicValueSecondPass( attributeMetadata, getBinding(), basicValue, bindingOptions, bindingState, bindingContext ) );

return basicValue;
}

private static void processColumn(
public static void bindImplicitJavaType(
MemberDetails member,
Property property,
@SuppressWarnings("unused") 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.binding() );
}
}

basicValue.addColumn( column );
@SuppressWarnings("unused") BindingOptions bindingOptions,
@SuppressWarnings("unused") BindingState bindingState,
@SuppressWarnings("unused") BindingContext bindingContext) {
basicValue.setImplicitJavaTypeAccess( (typeConfiguration) -> member.getType().toJavaClass() );
}

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> enumerated = member.getAnnotationUsage( Enumerated.class );
if ( enumerated == null ) {
return;
}

basicValue.setEnumerationStyle( enumerated.getEnum( "value", ORDINAL ) );
}

private static void processConversion(
public static void bindOptimisticLocking(
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<AttributeConverter<?,?>> 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<BasicJavaType<?>> 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()
);
Property property,
@SuppressWarnings("unused") BasicValue basicValue,
@SuppressWarnings("unused") BindingOptions bindingOptions,
@SuppressWarnings("unused") BindingState bindingState,
@SuppressWarnings("unused") BindingContext bindingContext) {
final var annotationUsage = member.getAnnotationUsage( OptimisticLock.class );
if ( annotationUsage != null ) {
if ( annotationUsage.getBoolean( "excluded" ) ) {
property.setOptimisticLocked( false );
return;
}

basicValue.setExplicitJdbcTypeAccess( (typeConfiguration) -> {
final var classDetails = jdbcTypeAnn.getClassDetails( "value" );
final Class<org.hibernate.type.descriptor.jdbc.JdbcType> 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 );
}

property.setOptimisticLocked( true );
}

private static void processMutability(
public static void bindMutability(
MemberDetails member,
Property property, BasicValue basicValue) {
Property property,
BasicValue basicValue,
@SuppressWarnings("unused") BindingOptions bindingOptions,
@SuppressWarnings("unused") BindingState bindingState,
@SuppressWarnings("unused") BindingContext bindingContext) {
final var mutabilityAnn = member.getAnnotationUsage( Mutability.class );
final var immutableAnn = member.getAnnotationUsage( Immutable.class );

Expand Down Expand Up @@ -263,21 +165,6 @@ else if ( mutabilityAnn != null ) {
}
}

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 {
Expand All @@ -294,30 +181,80 @@ private record BasicValueSecondPass(
AttributeMetadata attributeMetadata,
Property property,
BasicValue basicValue,
BindingOptions bindingOptions,
BindingState bindingState,
BindingContext bindingContext) implements ValueSecondPass {

@Override
public boolean processValue() {
final MemberDetails member = attributeMetadata.getMember();
processColumn( member, property, basicValue, bindingState, bindingContext );
processLob( member, basicValue );
processNationalized( member, basicValue );
processEnumerated( member, basicValue );
processConversion( member, basicValue, bindingContext );
processImplicitJavaType( member, basicValue );
processJavaType( member, basicValue );
processJdbcType( member, basicValue );
processMutability( member, property, basicValue );
processOptimisticLocking( member, property, basicValue );
processTemporalPrecision( member, basicValue );
processTimeZoneStorage( member, property, basicValue );

return true;
public boolean processValue() {
final MemberDetails member = attributeMetadata.getMember();
bindImplicitJavaType( member, property, basicValue, bindingOptions, bindingState, bindingContext );
bindMutability( member, property, basicValue, bindingOptions, bindingState, bindingContext );
bindOptimisticLocking( member, property, basicValue, bindingOptions, bindingState, bindingContext );
bindConversion( member, property, basicValue, bindingOptions, bindingState, bindingContext );

processColumn( member, property, basicValue, bindingState, bindingContext );

BasicValueBinder.bindJavaType( member, property, basicValue, bindingOptions, bindingState, bindingContext );
BasicValueBinder.bindJdbcType( member, property, basicValue, bindingOptions, bindingState, bindingContext );
BasicValueBinder.bindLob( member, property, basicValue, bindingOptions, bindingState, bindingContext );
BasicValueBinder.bindNationalized( member, property, basicValue, bindingOptions, bindingState, bindingContext );
BasicValueBinder.bindEnumerated( member, property, basicValue, bindingOptions, bindingState, bindingContext );
BasicValueBinder.bindTemporalPrecision( member, property, basicValue, bindingOptions, bindingState, bindingContext );
BasicValueBinder.bindTimeZoneStorage( member, property, basicValue, bindingOptions, bindingState, bindingContext );

return true;
}

private static void processColumn(
MemberDetails member,
Property property,
BasicValue basicValue,
BindingState bindingState,
@SuppressWarnings("unused") 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 );
if ( tableName != null ) {
final Identifier identifier = Identifier.toIdentifier( tableName );
final TableReference tableByName = bindingState.getTableByName( identifier.getCanonicalName() );
basicValue.setTable( tableByName.binding() );
}
}

basicValue.addColumn( column );
}

private static void bindConversion(
MemberDetails member,
@SuppressWarnings("unused") Property property,
@SuppressWarnings("unused") BasicValue basicValue,
@SuppressWarnings("unused") BindingOptions bindingOptions,
@SuppressWarnings("unused") BindingState bindingState,
@SuppressWarnings("unused") 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() );
}

private void processImplicitJavaType(MemberDetails member, BasicValue basicValue) {
basicValue.setImplicitJavaTypeAccess( (typeConfiguration) -> member.getType().toJavaClass() );
final ClassDetails converterClassDetails = convertAnn.getClassDetails( "converter" );
final Class<AttributeConverter<?,?>> javaClass = converterClassDetails.toJavaClass();
basicValue.setJpaAttributeConverterDescriptor( new ClassBasedConverterDescriptor(
javaClass,
bindingContext.getClassmateContext()
) );
}

private void processTemporalPrecision(MemberDetails member, BasicValue basicValue) {
Expand Down
Loading

0 comments on commit 0e6c653

Please sign in to comment.