diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributor.java index 8f2176812d9e..6e88ae1b471e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributor.java @@ -41,4 +41,21 @@ public interface TypeContributor { * @param serviceRegistry The service registry */ void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry); + + /** + * Determines order in which the contributions will be applied + * (lowest ordinal first). + *

+ * The range 0-500 is reserved for Hibernate, range 500-1000 for libraries and + * 1000-Integer.MAX_VALUE for user-defined TypeContributors. + *

+ * Contributions from higher precedence contributors (higher numbers) effectively override + * contributions from lower precedence. E.g. if a contributor with precedence 2000 contributes + * some type, that will override Hibernate's standard type of that name. + * + * @return the ordinal for this TypeContributor + */ + default int ordinal(){ + return 1000; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java index 68ccbb66f502..bbb70ee32b01 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java @@ -87,6 +87,7 @@ import jakarta.persistence.AttributeConverter; +import static java.util.Comparator.comparingInt; import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForArray; import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForDuration; import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForInstant; @@ -642,7 +643,7 @@ public void contributeType(CompositeUserType type) { final JdbcType dialectArrayDescriptor = jdbcTypeRegistry.findDescriptor( SqlTypes.ARRAY ); // add TypeContributor contributed types. - for ( TypeContributor contributor : classLoaderService.loadJavaServices( TypeContributor.class ) ) { + for ( TypeContributor contributor : sortedTypeContributors( classLoaderService ) ) { contributor.contribute( typeContributions, options.getServiceRegistry() ); } @@ -749,6 +750,17 @@ public void contributeType(CompositeUserType type) { } } + private static List sortedTypeContributors( + ClassLoaderService classLoaderService) { + Collection typeContributors = classLoaderService.loadJavaServices( TypeContributor.class ); + List contributors = new ArrayList<>( typeContributors ); + contributors.sort( + comparingInt( TypeContributor::ordinal ) + .thenComparing( a -> a.getClass().getCanonicalName() ) + ); + return contributors; + } + private static void adaptToPreferredSqlTypeCode( JdbcTypeRegistry jdbcTypeRegistry, JdbcType dialectUuidDescriptor, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/contributor/usertype/TypeContributionOrdinalTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/contributor/usertype/TypeContributionOrdinalTest.java new file mode 100644 index 000000000000..7fcdec7a2c14 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/contributor/usertype/TypeContributionOrdinalTest.java @@ -0,0 +1,77 @@ +package org.hibernate.orm.test.type.contributor.usertype; + +import org.hibernate.boot.model.TypeContributions; +import org.hibernate.boot.model.TypeContributor; +import org.hibernate.service.ServiceRegistry; + +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; + +import org.hibernate.type.descriptor.jdbc.VarcharJdbcType; +import org.hibernate.type.spi.TypeConfiguration; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test to assert that service loaded types are processed in ascending ordinal order + * + * @author Steven Barendregt + */ +@DomainModel +@SessionFactory +@BootstrapServiceRegistry( + javaServices = { + @BootstrapServiceRegistry.JavaService(role = TypeContributor.class, impl = TypeContributionOrdinalTest.HigherOrdinalServiceLoadedVarcharTypeContributor.class), + @BootstrapServiceRegistry.JavaService(role = TypeContributor.class, impl = TypeContributionOrdinalTest.ServiceLoadedVarcharTypeContributor.class) + } +) +public class TypeContributionOrdinalTest { + + @Test + @Jira(value = "https://hibernate.atlassian.net/issues/HHH-19247") + public void testHigherOrdinalServiceLoadedCustomUserTypeTakesPrecedence(SessionFactoryScope scope) { + final TypeConfiguration typeConfigurations = scope.getSessionFactory() + .getMappingMetamodel() + .getTypeConfiguration(); + Assertions.assertInstanceOf( + HigherOrdinalServiceLoadedVarcharTypeContributor.HigherOrdinalExtendedVarcharJdbcType.class, + typeConfigurations.getJdbcTypeRegistry().findDescriptor( 12 ) + ); + } + + public static class ServiceLoadedVarcharTypeContributor implements TypeContributor { + + @Override + public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + typeContributions.contributeJdbcType( ExtendedVarcharJdbcType.INSTANCE ); + } + + public static class ExtendedVarcharJdbcType extends VarcharJdbcType { + + public static final ExtendedVarcharJdbcType INSTANCE = new ExtendedVarcharJdbcType(); + } + + } + + public static class HigherOrdinalServiceLoadedVarcharTypeContributor implements TypeContributor { + + @Override + public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + typeContributions.contributeJdbcType( HigherOrdinalExtendedVarcharJdbcType.INSTANCE ); + } + + @Override + public int ordinal() { + return 2000; + } + + public static class HigherOrdinalExtendedVarcharJdbcType extends VarcharJdbcType { + + public static final HigherOrdinalExtendedVarcharJdbcType INSTANCE = new HigherOrdinalExtendedVarcharJdbcType(); + } + } +}