Skip to content

Commit f7dbe58

Browse files
committed
Throw meaningful exceptions if either our dialect or connection provider is not plugged in
HIBERNATE-135
1 parent 9d11a0b commit f7dbe58

File tree

8 files changed

+168
-93
lines changed

8 files changed

+168
-93
lines changed

build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ dependencies {
177177
exclude(group = "org.apache.logging.log4j", module = "log4j-core")
178178
}
179179
integrationTestRuntimeOnly("org.junit.platform:junit-platform-launcher")
180-
integrationTestRuntimeOnly(libs.postgresql)
181180

182181
api(libs.jspecify)
183182

gradle/libs.versions.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ slf4j-api = "2.0.16"
2525
logback-classic = "1.5.16"
2626
mockito = "5.16.0"
2727
checker-qual = "3.49.1"
28-
postgresql = "42.7.8"
2928

3029
plugin-spotless = "7.0.2"
3130
plugin-errorprone = "4.1.0"
@@ -48,7 +47,6 @@ sl4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" }
4847
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-classic" }
4948
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }
5049
checker-qual = { module = "org.checkerframework:checker-qual", version.ref = "checker-qual" }
51-
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
5250

5351
[bundles]
5452
test-common = ["junit-jupiter", "assertj", "logback-classic"]

src/integrationTest/java/com/mongodb/hibernate/SessionFactoryIntegrationTests.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ void testSuccess() {
3636

3737
@Test
3838
void testInvalidConnectionString() {
39-
assertThrows(ServiceException.class, () -> buildSessionFactory(
40-
Map.of(JAKARTA_JDBC_URL, "jdbc:postgresql://localhost/test"))
41-
.close());
39+
assertThrows(
40+
ServiceException.class, () -> buildSessionFactory(Map.of(JAKARTA_JDBC_URL, "jdbc:postgresql://host/"))
41+
.close());
4242
}
4343

4444
@Test
@@ -60,12 +60,10 @@ private static SessionFactory buildSessionFactory() throws ServiceException {
6060
}
6161

6262
private static SessionFactory buildSessionFactory(Map<String, Object> settings) throws ServiceException {
63-
var standardServiceRegistry =
64-
new StandardServiceRegistryBuilder().applySettings(settings).build();
65-
return new MetadataSources(standardServiceRegistry)
66-
.getMetadataBuilder()
67-
.build()
68-
.getSessionFactoryBuilder()
69-
.build();
63+
return new MetadataSources()
64+
.buildMetadata(new StandardServiceRegistryBuilder()
65+
.applySettings(settings)
66+
.build())
67+
.buildSessionFactory();
7068
}
7169
}

src/integrationTest/java/com/mongodb/hibernate/boot/PostgreSQLBootstrappingIntegrationTests.java

Lines changed: 0 additions & 73 deletions
This file was deleted.

src/main/java/com/mongodb/hibernate/internal/service/StandardServiceRegistryScopedState.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
import static org.hibernate.cfg.AvailableSettings.JAVA_TIME_USE_DIRECT_JDBC;
2323
import static org.hibernate.cfg.AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE;
2424

25+
import com.mongodb.hibernate.dialect.MongoDialect;
2526
import com.mongodb.hibernate.internal.VisibleForTesting;
2627
import com.mongodb.hibernate.internal.cfg.MongoConfiguration;
2728
import com.mongodb.hibernate.internal.cfg.MongoConfigurationBuilder;
29+
import com.mongodb.hibernate.jdbc.MongoConnectionProvider;
2830
import com.mongodb.hibernate.service.spi.MongoConfigurationContributor;
2931
import java.io.IOException;
3032
import java.io.NotSerializableException;
@@ -35,6 +37,8 @@
3537
import org.hibernate.HibernateException;
3638
import org.hibernate.boot.registry.StandardServiceInitiator;
3739
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
40+
import org.hibernate.cfg.AvailableSettings;
41+
import org.hibernate.engine.jdbc.dialect.spi.DialectFactory;
3842
import org.hibernate.service.Service;
3943
import org.hibernate.service.UnknownServiceException;
4044
import org.hibernate.service.spi.ServiceRegistryImplementor;
@@ -75,13 +79,49 @@ public Class<StandardServiceRegistryScopedState> getServiceInitiated() {
7579
@Override
7680
public StandardServiceRegistryScopedState initiateService(
7781
Map<String, Object> configurationValues, ServiceRegistryImplementor serviceRegistry) {
82+
checkMongoDialectIsPluggedIn(configurationValues, serviceRegistry);
83+
checkMongoConnectionProviderIsPluggedIn(configurationValues);
7884
return new StandardServiceRegistryScopedState(
7985
createMongoConfiguration(configurationValues, serviceRegistry));
8086
}
8187
});
8288
}
8389

84-
private MongoConfiguration createMongoConfiguration(
90+
private static void checkMongoDialectIsPluggedIn(
91+
Map<String, Object> configurationValues, ServiceRegistryImplementor serviceRegistry) {
92+
var dialectFactory = serviceRegistry.getService(DialectFactory.class);
93+
if ((dialectFactory == null
94+
|| dialectFactory.getClass().getPackageName().startsWith("org.hibernate"))
95+
&& configurationValues.get(AvailableSettings.DIALECT_RESOLVERS) == null) {
96+
// If `DialectFactory` is different from the ones Hibernate ORM provide, or if
97+
// `AvailableSettings.DIALECT_RESOLVERS` is specified, then we cannot detect whether
98+
// `MongoDialect` is plugged in. Otherwise, we know that `AvailableSettings.DIALECT`
99+
// is the only way to plug `MongoDialect` in, and we can detect whether it is plugged in.
100+
var dialect = configurationValues.get(AvailableSettings.DIALECT);
101+
if (!((dialect instanceof MongoDialect)
102+
|| (dialect instanceof Class<?> dialectClass
103+
&& MongoDialect.class.isAssignableFrom(dialectClass))
104+
|| (dialect instanceof String dialectName
105+
&& dialectName.startsWith("com.mongodb.hibernate")))) {
106+
throw new RuntimeException("%s must be plugged in, for example, via the [%s] configuration property"
107+
.formatted(MongoDialect.class.getName(), AvailableSettings.DIALECT));
108+
}
109+
}
110+
}
111+
112+
private static void checkMongoConnectionProviderIsPluggedIn(Map<String, Object> configurationValues) {
113+
var connectionProvider = configurationValues.get(AvailableSettings.CONNECTION_PROVIDER);
114+
if (!((connectionProvider instanceof MongoConnectionProvider)
115+
|| (connectionProvider instanceof Class<?> connectionProviderClass
116+
&& MongoConnectionProvider.class.isAssignableFrom(connectionProviderClass))
117+
|| (connectionProvider instanceof String connectionProviderName
118+
&& connectionProviderName.startsWith("com.mongodb.hibernate")))) {
119+
throw new RuntimeException("%s must be plugged in, for example, via the [%s] configuration property"
120+
.formatted(MongoConnectionProvider.class.getName(), AvailableSettings.CONNECTION_PROVIDER));
121+
}
122+
}
123+
124+
private static MongoConfiguration createMongoConfiguration(
85125
Map<String, Object> configurationValues, ServiceRegistryImplementor serviceRegistry) {
86126
var jdbcUrl = configurationValues.get(JAKARTA_JDBC_URL);
87127
var mongoConfigurationContributor = getMongoConfigurationContributor(serviceRegistry);
@@ -98,7 +138,7 @@ private MongoConfiguration createMongoConfiguration(
98138
return mongoConfigurationBuilder.build();
99139
}
100140

101-
private @Nullable MongoConfigurationContributor getMongoConfigurationContributor(
141+
private static @Nullable MongoConfigurationContributor getMongoConfigurationContributor(
102142
ServiceRegistryImplementor serviceRegistry) {
103143
MongoConfigurationContributor result = null;
104144
try {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2024-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.hibernate.boot;
18+
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20+
import static org.hibernate.cfg.AvailableSettings.ALLOW_METADATA_ON_BOOT;
21+
import static org.hibernate.cfg.AvailableSettings.DIALECT;
22+
import static org.hibernate.cfg.AvailableSettings.JAKARTA_JDBC_URL;
23+
24+
import com.mongodb.hibernate.internal.boot.MongoAdditionalMappingContributor;
25+
import jakarta.persistence.Embeddable;
26+
import jakarta.persistence.Entity;
27+
import jakarta.persistence.Id;
28+
import jakarta.persistence.Table;
29+
import org.hibernate.InstantiationException;
30+
import org.hibernate.boot.MetadataSources;
31+
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
32+
import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
33+
import org.junit.jupiter.api.Test;
34+
35+
class NativeBootstrappingTests {
36+
@Test
37+
void testMongoDialectNotPluggedIn() {
38+
try (var standardServiceRegistry = new StandardServiceRegistryBuilder()
39+
.applySetting(DIALECT, "org.hibernate.dialect.PostgreSQLDialect")
40+
.build()) {
41+
assertThatThrownBy(() -> new MetadataSources()
42+
.buildMetadata(standardServiceRegistry)
43+
.buildSessionFactory()
44+
.close())
45+
.hasRootCauseMessage("com.mongodb.hibernate.dialect.MongoDialect must be plugged in"
46+
+ ", for example, via the [hibernate.dialect] configuration property");
47+
}
48+
}
49+
50+
/**
51+
* Verify that {@link MongoAdditionalMappingContributor} skips its logic when bootstrapping is unrelated to the
52+
* MongoDB Extension for Hibernate ORM.
53+
*/
54+
@Test
55+
void testMongoAdditionalMappingContributorIsSkipped() {
56+
var standardServiceRegistryBuilder = new StandardServiceRegistryBuilder();
57+
standardServiceRegistryBuilder.clearSettings();
58+
try (var sessionFactory = new MetadataSources()
59+
.addAnnotatedClass(Item.class)
60+
.buildMetadata(standardServiceRegistryBuilder
61+
.applySetting(DIALECT, "org.hibernate.dialect.PostgreSQLDialect")
62+
.applySetting(JAKARTA_JDBC_URL, "jdbc:postgresql://host/")
63+
.applySetting(ALLOW_METADATA_ON_BOOT, false)
64+
.applySetting(DriverManagerConnectionProviderImpl.INITIAL_SIZE, 0)
65+
.build())
66+
.buildSessionFactory()) {
67+
assertThatThrownBy(() -> sessionFactory.inSession(session -> session.persist(new Item(null))))
68+
.hasMessageNotContaining("does not support primary key spanning multiple columns")
69+
.isInstanceOf(InstantiationException.class)
70+
.hasMessageMatching("Could not instantiate entity .* due to: null");
71+
}
72+
}
73+
74+
@Entity
75+
@Table(name = Item.COLLECTION_NAME)
76+
static class Item {
77+
static final String COLLECTION_NAME = "items";
78+
79+
@Id
80+
MultipleColumns id;
81+
82+
Item(MultipleColumns id) {
83+
this.id = id;
84+
}
85+
86+
@Embeddable
87+
record MultipleColumns(int a, int b) {}
88+
}
89+
}

src/test/java/com/mongodb/hibernate/internal/service/StandardServiceRegistryScopedStateTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.mongodb.hibernate.internal.service;
1818

19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20+
import static org.hibernate.cfg.AvailableSettings.DIALECT;
1921
import static org.junit.jupiter.api.Assertions.assertEquals;
2022
import static org.junit.jupiter.api.Assertions.assertNotSame;
2123

@@ -65,4 +67,28 @@ void mongoConfigurationContributorInvocationsAndMongoConfiguratorInstances() {
6567
}
6668
}
6769
}
70+
71+
@Test
72+
void testMongoDialectNotPluggedIn() {
73+
var standardServiceRegistryBuilder = new StandardServiceRegistryBuilder();
74+
standardServiceRegistryBuilder.clearSettings();
75+
try (var standardServiceRegistry = standardServiceRegistryBuilder.build()) {
76+
assertThatThrownBy(() -> standardServiceRegistry.requireService(StandardServiceRegistryScopedState.class))
77+
.hasRootCauseMessage("com.mongodb.hibernate.dialect.MongoDialect must be plugged in"
78+
+ ", for example, via the [hibernate.dialect] configuration property");
79+
}
80+
}
81+
82+
@Test
83+
void testMongoConnectionProviderNotPluggedIn() {
84+
var standardServiceRegistryBuilder = new StandardServiceRegistryBuilder();
85+
standardServiceRegistryBuilder.clearSettings();
86+
try (var standardServiceRegistry = standardServiceRegistryBuilder
87+
.applySetting(DIALECT, "com.mongodb.hibernate.dialect.MongoDialect")
88+
.build()) {
89+
assertThatThrownBy(() -> standardServiceRegistry.requireService(StandardServiceRegistryScopedState.class))
90+
.hasRootCauseMessage("com.mongodb.hibernate.jdbc.MongoConnectionProvider must be plugged in"
91+
+ ", for example, via the [hibernate.connection.provider_class] configuration property");
92+
}
93+
}
6894
}

src/test/java/com/mongodb/hibernate/service/MongoConfigurationContributorTests.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,11 @@ void exceptionPropagates() {
6060
}
6161

6262
private static SessionFactory buildSessionFactory(MongoConfigurationContributor mongoConfigurationContributor) {
63-
return new MetadataSources(new StandardServiceRegistryBuilder()
63+
return new MetadataSources()
64+
.buildMetadata(new StandardServiceRegistryBuilder()
6465
.addService(MongoConfigurationContributor.class, mongoConfigurationContributor)
6566
.build())
66-
.getMetadataBuilder()
67-
.build()
68-
.getSessionFactoryBuilder()
69-
.build();
67+
.buildSessionFactory();
7068
}
7169

7270
private static class TestClusterListener implements ClusterListener {

0 commit comments

Comments
 (0)