Skip to content

Commit cb4399e

Browse files
committed
HHH-19247 Add ordinal() also to Type contributions to be able to influence the order in which serviceloaded custom types are registered
1 parent 702d154 commit cb4399e

File tree

5 files changed

+166
-47
lines changed

5 files changed

+166
-47
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/FunctionContributor.java

+2-20
Original file line numberDiff line numberDiff line change
@@ -31,34 +31,16 @@
3131
* or even {@link org.hibernate.boot.MetadataBuilder#applyFunctions(FunctionContributor)}.
3232
* </ul>
3333
*
34-
* @see org.hibernate.query.sqm.function.SqmFunctionRegistry
35-
*
3634
* @author Karel Maesen
35+
* @see org.hibernate.query.sqm.function.SqmFunctionRegistry
3736
*/
3837
@JavaServiceLoadable
39-
public interface FunctionContributor {
38+
public interface FunctionContributor extends Ordinated {
4039

4140
/**
4241
* Contribute functions
4342
*
4443
* @param functionContributions The target for the contributions
4544
*/
4645
void contributeFunctions(FunctionContributions functionContributions);
47-
48-
/**
49-
* Determines order in which the contributions will be applied
50-
* (lowest ordinal first).
51-
* <p>
52-
* The range 0-500 is reserved for Hibernate, range 500-1000 for libraries and
53-
* 1000-Integer.MAX_VALUE for user-defined FunctionContributors.
54-
* <p>
55-
* Contributions from higher precedence contributors (higher numbers) effectively override
56-
* contributions from lower precedence. E.g. if a contributor with precedence 1000 contributes a
57-
* function named {@code "max"}, that will override Hibernate's standard function of that name.
58-
*
59-
* @return the ordinal for this FunctionContributor
60-
*/
61-
default int ordinal(){
62-
return 1000;
63-
}
6446
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.boot.model;
8+
9+
/**
10+
* Interface that defines the loading order of a contribution
11+
*
12+
* @author Steven Barendregt
13+
*/
14+
public interface Ordinated {
15+
16+
/**
17+
* Determines order in which contributions will be applied
18+
* (lowest ordinal first).
19+
* <p>
20+
* The range 0-500 is reserved for Hibernate, range 500-1000 for libraries and
21+
* 1000-Integer.MAX_VALUE for user-defined Contributors.
22+
* <p>
23+
* Contributions from higher precedence contributors (higher numbers) effectively override
24+
* contributions from lower precedence.
25+
*
26+
* @return the ordinal for a Contributor
27+
*/
28+
default int ordinal() {
29+
return 1000;
30+
}
31+
32+
}

hibernate-core/src/main/java/org/hibernate/boot/model/TypeContributor.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,16 @@
2323
* or even {@link org.hibernate.boot.MetadataBuilder#applyTypes(TypeContributor)}.
2424
* <li>
2525
* When bootstrapping Hibernate via JPA or {@link org.hibernate.cfg.Configuration},
26-
*
26+
* <p>
2727
* Finally, in the JPA boostrap process, {@code TypeContributor}s may be
2828
* listed via {@link org.hibernate.jpa.boot.spi.JpaSettings#TYPE_CONTRIBUTORS}.
2929
* </ul>
3030
*
3131
* @author Steve Ebersole
32-
*
3332
* @see org.hibernate.type.spi.TypeConfiguration
3433
*/
3534
@JavaServiceLoadable
36-
public interface TypeContributor {
35+
public interface TypeContributor extends Ordinated {
3736
/**
3837
* Contribute types
3938
*

hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java

+53-24
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787

8888
import jakarta.persistence.AttributeConverter;
8989

90+
import static java.util.Comparator.comparingInt;
9091
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForArray;
9192
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForDuration;
9293
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForInstant;
@@ -95,18 +96,18 @@
9596
/**
9697
* Represents the process of transforming a {@link MetadataSources}
9798
* reference into a {@link org.hibernate.boot.Metadata} reference. Allows for 2 different process paradigms:<ul>
98-
* <li>
99-
* Single step : as defined by the {@link #build} method; internally leverages the 2-step paradigm
100-
* </li>
101-
* <li>
102-
* Two step : a first step coordinates resource scanning and some other preparation work; a second step
103-
* builds the {@link org.hibernate.boot.Metadata}. A hugely important distinction in the need for the
104-
* steps is that the first phase should strive to not load user entity/component classes so that we can still
105-
* perform enhancement on them later. This approach caters to the 2-phase bootstrap we use in regards
106-
* to WildFly Hibernate-JPA integration. The first step is defined by {@link #prepare} which returns
107-
* a {@link ManagedResources} instance. The second step is defined by calling
108-
* {@link #complete}
109-
* </li>
99+
* <li>
100+
* Single step : as defined by the {@link #build} method; internally leverages the 2-step paradigm
101+
* </li>
102+
* <li>
103+
* Two step : a first step coordinates resource scanning and some other preparation work; a second step
104+
* builds the {@link org.hibernate.boot.Metadata}. A hugely important distinction in the need for the
105+
* steps is that the first phase should strive to not load user entity/component classes so that we can still
106+
* perform enhancement on them later. This approach caters to the 2-phase bootstrap we use in regards
107+
* to WildFly Hibernate-JPA integration. The first step is defined by {@link #prepare} which returns
108+
* a {@link ManagedResources} instance. The second step is defined by calling
109+
* {@link #complete}
110+
* </li>
110111
* </ul>
111112
*
112113
* @author Steve Ebersole
@@ -199,7 +200,7 @@ public static MetadataImplementor complete(
199200

200201
final MetadataSourceProcessor processor = new MetadataSourceProcessor() {
201202
private final MetadataSourceProcessor hbmProcessor =
202-
options.isXmlMappingEnabled()
203+
options.isXmlMappingEnabled()
203204
? new HbmMetadataSourceProcessorImpl( managedResources, rootMetadataBuildingContext )
204205
: new NoOpMetadataSourceProcessorImpl();
205206

@@ -338,8 +339,19 @@ public void finishUp() {
338339

339340
processor.finishUp();
340341

341-
processAdditionalMappingContributions( metadataCollector, options, classLoaderService, rootMetadataBuildingContext );
342-
processAdditionalJaxbMappingProducer( metadataCollector, options, jandexView, classLoaderService, rootMetadataBuildingContext );
342+
processAdditionalMappingContributions(
343+
metadataCollector,
344+
options,
345+
classLoaderService,
346+
rootMetadataBuildingContext
347+
);
348+
processAdditionalJaxbMappingProducer(
349+
metadataCollector,
350+
options,
351+
jandexView,
352+
classLoaderService,
353+
rootMetadataBuildingContext
354+
);
343355

344356
applyExtraQueryImports( managedResources, metadataCollector );
345357

@@ -379,7 +391,8 @@ public boolean transformHbmMappings() {
379391
rootMetadataBuildingContext
380392
);
381393

382-
final Collection<AdditionalMappingContributor> additionalMappingContributors = classLoaderService.loadJavaServices( AdditionalMappingContributor.class );
394+
final Collection<AdditionalMappingContributor> additionalMappingContributors = classLoaderService.loadJavaServices(
395+
AdditionalMappingContributor.class );
383396
additionalMappingContributors.forEach( (contributor) -> {
384397
contributions.setCurrentContributor( contributor.getContributorName() );
385398
try {
@@ -450,7 +463,7 @@ public void contributeBinding(InputStream xmlStream) {
450463

451464
@Override
452465
public void contributeBinding(JaxbEntityMappings mappingJaxbBinding) {
453-
if ( ! options.isXmlMappingEnabled() ) {
466+
if ( !options.isXmlMappingEnabled() ) {
454467
return;
455468
}
456469

@@ -462,7 +475,7 @@ public void contributeBinding(JaxbEntityMappings mappingJaxbBinding) {
462475

463476
@Override
464477
public void contributeBinding(JaxbHbmHibernateMapping hbmJaxbBinding) {
465-
if ( ! options.isXmlMappingEnabled() ) {
478+
if ( !options.isXmlMappingEnabled() ) {
466479
return;
467480
}
468481

@@ -527,7 +540,8 @@ private static void processAdditionalJaxbMappingProducer(
527540
ClassLoaderService classLoaderService,
528541
MetadataBuildingContextRootImpl rootMetadataBuildingContext) {
529542
if ( options.isXmlMappingEnabled() ) {
530-
final Iterable<AdditionalJaxbMappingProducer> producers = classLoaderService.loadJavaServices( AdditionalJaxbMappingProducer.class );
543+
final Iterable<AdditionalJaxbMappingProducer> producers = classLoaderService.loadJavaServices(
544+
AdditionalJaxbMappingProducer.class );
531545
if ( producers != null ) {
532546
final EntityHierarchyBuilder hierarchyBuilder = new EntityHierarchyBuilder();
533547
final MappingBinder mappingBinder = new MappingBinder(
@@ -597,7 +611,7 @@ private static void handleTypes(
597611
MetadataBuildingOptions options,
598612
InFlightMetadataCollector metadataCollector) {
599613
final ClassLoaderService classLoaderService =
600-
options.getServiceRegistry().requireService(ClassLoaderService.class);
614+
options.getServiceRegistry().requireService( ClassLoaderService.class );
601615

602616
final TypeConfiguration typeConfiguration = bootstrapContext.getTypeConfiguration();
603617
final StandardServiceRegistry serviceRegistry = bootstrapContext.getServiceRegistry();
@@ -631,7 +645,7 @@ public void contributeType(CompositeUserType<?> type) {
631645
basicTypeRegistry.addTypeReferenceRegistrationKey(
632646
StandardBasicTypes.BINARY_WRAPPER.getName(),
633647
Byte[].class.getName(), "Byte[]"
634-
);
648+
);
635649
}
636650

637651
// add Dialect contributed types
@@ -642,7 +656,7 @@ public void contributeType(CompositeUserType<?> type) {
642656
final JdbcType dialectArrayDescriptor = jdbcTypeRegistry.findDescriptor( SqlTypes.ARRAY );
643657

644658
// add TypeContributor contributed types.
645-
for ( TypeContributor contributor : classLoaderService.loadJavaServices( TypeContributor.class ) ) {
659+
for ( TypeContributor contributor : sortedTypeContributors( classLoaderService ) ) {
646660
contributor.contribute( typeContributions, options.getServiceRegistry() );
647661
}
648662

@@ -749,6 +763,17 @@ public void contributeType(CompositeUserType<?> type) {
749763
}
750764
}
751765

766+
private static List<TypeContributor> sortedTypeContributors(
767+
ClassLoaderService classLoaderService) {
768+
Collection<TypeContributor> typeContributors = classLoaderService.loadJavaServices( TypeContributor.class );
769+
List<TypeContributor> contributors = new ArrayList<>( typeContributors );
770+
contributors.sort(
771+
comparingInt( TypeContributor::ordinal )
772+
.thenComparing( a -> a.getClass().getCanonicalName() )
773+
);
774+
return contributors;
775+
}
776+
752777
private static void adaptToPreferredSqlTypeCode(
753778
JdbcTypeRegistry jdbcTypeRegistry,
754779
JdbcType dialectUuidDescriptor,
@@ -830,7 +855,9 @@ private static void adaptTimestampTypesToDefaultTimeZoneStorage(
830855
);
831856
}
832857

833-
private static JdbcType getTimeWithTimeZoneOverride(MetadataBuildingOptions options, JdbcTypeRegistry jdbcTypeRegistry) {
858+
private static JdbcType getTimeWithTimeZoneOverride(
859+
MetadataBuildingOptions options,
860+
JdbcTypeRegistry jdbcTypeRegistry) {
834861
switch ( options.getDefaultTimeZoneStorage() ) {
835862
case NORMALIZE:
836863
// For NORMALIZE, we replace the standard types that use TIME_WITH_TIMEZONE to use TIME
@@ -843,7 +870,9 @@ private static JdbcType getTimeWithTimeZoneOverride(MetadataBuildingOptions opti
843870
}
844871
}
845872

846-
private static JdbcType getTimestampWithTimeZoneOverride(MetadataBuildingOptions options, JdbcTypeRegistry jdbcTypeRegistry) {
873+
private static JdbcType getTimestampWithTimeZoneOverride(
874+
MetadataBuildingOptions options,
875+
JdbcTypeRegistry jdbcTypeRegistry) {
847876
switch ( options.getDefaultTimeZoneStorage() ) {
848877
case NORMALIZE:
849878
// For NORMALIZE, we replace the standard types that use TIMESTAMP_WITH_TIMEZONE to use TIMESTAMP
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package org.hibernate.orm.test.type.contributor.usertype;
2+
3+
import org.hibernate.boot.model.TypeContributions;
4+
import org.hibernate.boot.model.TypeContributor;
5+
import org.hibernate.service.ServiceRegistry;
6+
7+
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
8+
import org.hibernate.testing.orm.junit.DomainModel;
9+
import org.hibernate.testing.orm.junit.Jira;
10+
import org.hibernate.testing.orm.junit.SessionFactory;
11+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
12+
13+
import org.hibernate.type.descriptor.jdbc.VarcharJdbcType;
14+
import org.hibernate.type.spi.TypeConfiguration;
15+
16+
import org.junit.jupiter.api.Assertions;
17+
import org.junit.jupiter.api.Test;
18+
19+
/**
20+
* Test to assert that service loaded types are processed in ascending ordinal order
21+
*
22+
* @author Steven Barendregt
23+
*/
24+
@DomainModel
25+
@SessionFactory
26+
@BootstrapServiceRegistry(
27+
javaServices = {
28+
@BootstrapServiceRegistry.JavaService(role = TypeContributor.class, impl = TypeContributionOrdinalTest.HigherOrdinalServiceLoadedVarcharTypeContributor.class),
29+
@BootstrapServiceRegistry.JavaService(role = TypeContributor.class, impl = TypeContributionOrdinalTest.ServiceLoadedVarcharTypeContributor.class)
30+
}
31+
)
32+
public class TypeContributionOrdinalTest {
33+
34+
@Test
35+
@Jira(value = "https://hibernate.atlassian.net/issues/HHH-19247")
36+
public void testHigherOrdinalServiceLoadedCustomUserTypeTakesPrecedence(SessionFactoryScope scope) {
37+
final TypeConfiguration typeConfigurations = scope.getSessionFactory()
38+
.getMappingMetamodel()
39+
.getTypeConfiguration();
40+
Assertions.assertInstanceOf(
41+
HigherOrdinalServiceLoadedVarcharTypeContributor.HigherOrdinalExtendedVarcharJdbcType.class,
42+
typeConfigurations.getJdbcTypeRegistry().findDescriptor( 12 )
43+
);
44+
}
45+
46+
public static class ServiceLoadedVarcharTypeContributor implements TypeContributor {
47+
48+
@Override
49+
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
50+
typeContributions.contributeJdbcType( ExtendedVarcharJdbcType.INSTANCE );
51+
}
52+
53+
public static class ExtendedVarcharJdbcType extends VarcharJdbcType {
54+
55+
public static final ExtendedVarcharJdbcType INSTANCE = new ExtendedVarcharJdbcType();
56+
}
57+
58+
}
59+
60+
public static class HigherOrdinalServiceLoadedVarcharTypeContributor implements TypeContributor {
61+
62+
@Override
63+
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
64+
typeContributions.contributeJdbcType( HigherOrdinalExtendedVarcharJdbcType.INSTANCE );
65+
}
66+
67+
@Override
68+
public int ordinal() {
69+
return 2000;
70+
}
71+
72+
public static class HigherOrdinalExtendedVarcharJdbcType extends VarcharJdbcType {
73+
74+
public static final HigherOrdinalExtendedVarcharJdbcType INSTANCE = new HigherOrdinalExtendedVarcharJdbcType();
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)