Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement ConnectionProvider #8

Merged
merged 42 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
da08c43
implement ConnectionProvider
NathanQingyangXu Oct 25, 2024
2c90afc
change minimal MongoDB version to 6
NathanQingyangXu Oct 28, 2024
cdba9c5
Update src/test/resources/hibernate.properties
NathanQingyangXu Nov 1, 2024
ad28ab1
remove package-info.java in test folder;
NathanQingyangXu Nov 1, 2024
bb51724
improve Javadoc for MongoConnectionProvider per review comments
NathanQingyangXu Nov 1, 2024
5fb9c5e
remove unnecessary codec setting stuff
NathanQingyangXu Nov 1, 2024
08a9603
simplify the dummy implementation of the three methods irrelevant to …
NathanQingyangXu Nov 1, 2024
77b0a72
simplify MongoConnectionProvider per code review comments
NathanQingyangXu Nov 1, 2024
46e06e4
remove exception package and move ConfigurationException to cfg package
NathanQingyangXu Nov 1, 2024
d68a0db
add missing Javadocs for MongoDialect
NathanQingyangXu Nov 1, 2024
b816075
add MQL acronym expansion in Javadoc
NathanQingyangXu Nov 1, 2024
e151069
round #2 code review comments
NathanQingyangXu Nov 4, 2024
bbd90f8
change mininum mongodb version to v6.3
NathanQingyangXu Nov 4, 2024
4036693
use HibernateException instead of ConfigurationException.java
NathanQingyangXu Nov 4, 2024
9c9622c
switch MongoDialect minimum db version to 6.0
NathanQingyangXu Nov 5, 2024
a97f2dd
Update src/main/java/com/mongodb/hibernate/jdbc/MongoConnectionProvid…
NathanQingyangXu Nov 5, 2024
4f9f958
deleted unused constructors in MongoDialect
NathanQingyangXu Nov 5, 2024
9b817a5
add Javadoc for the root package-info.java
NathanQingyangXu Nov 5, 2024
6ad89a0
make more exception messages begin with uppercase
NathanQingyangXu Nov 6, 2024
cdf49b0
update various gradle plugin to latest versions
NathanQingyangXu Nov 6, 2024
a73cf04
add sl4j logging dependencies and logback-test.xml config
NathanQingyangXu Nov 6, 2024
5c11f6c
fix a static check issue
NathanQingyangXu Nov 6, 2024
e366ea6
change default constructor of MongoDialect to end up with null value;…
NathanQingyangXu Nov 7, 2024
452c559
some minor improvements
NathanQingyangXu Nov 8, 2024
2124619
change as per Valentin's excellent code review comments
NathanQingyangXu Nov 8, 2024
196880e
update SessionFactoryTests as per code review comments
NathanQingyangXu Nov 9, 2024
8ac4c4f
update MongoDialect as per code review comments
NathanQingyangXu Nov 9, 2024
8e5eb59
update MongoConnectionProvider as per code review comments
NathanQingyangXu Nov 9, 2024
4756ea9
delete .editorconfig file and move it to another separate PR
NathanQingyangXu Nov 12, 2024
5a7ebed
fix a static check style issue
NathanQingyangXu Nov 12, 2024
51e3184
resolve some code review comments
NathanQingyangXu Nov 12, 2024
5307610
fix style checking issue
NathanQingyangXu Nov 12, 2024
7d11aa1
minor changes to improve Dialect min version usage
NathanQingyangXu Nov 13, 2024
8b3eefe
improve mentioning of MongoDB Java Driver in MongoDialect's Javadoc
NathanQingyangXu Nov 13, 2024
250673b
add transient keyword to MongoClient in MongoConnectionProvider
NathanQingyangXu Nov 13, 2024
b7c37ca
Update src/main/java/com/mongodb/hibernate/jdbc/MongoConnectionProvid…
NathanQingyangXu Nov 13, 2024
089ba52
change as per code review comments
NathanQingyangXu Nov 13, 2024
8e0d8e9
remove database checking logic
NathanQingyangXu Nov 13, 2024
1af2884
fix import style issue
NathanQingyangXu Nov 13, 2024
8dd11e2
change per some code review comments
NathanQingyangXu Nov 13, 2024
e831a88
Merge branch 'main' into HIBERNATE-27
NathanQingyangXu Nov 14, 2024
b9c98ff
add missing 'compileJava' gradle task in evergreen's static-check.sh
NathanQingyangXu Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,16 @@
import net.ltgt.gradle.errorprone.errorprone

plugins {
// Apply the java-library plugin for API and implementation separation.
`java-library`
alias(libs.plugins.spotless)
alias(libs.plugins.errorprone)
}

repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}

dependencies {
// Use JUnit Jupiter for testing.
testImplementation(libs.junit.jupiter)

testRuntimeOnly("org.junit.platform:junit-platform-launcher")
Expand All @@ -38,17 +35,18 @@ dependencies {
api(libs.jspecify)

errorprone(libs.google.errorprone.core)

implementation(libs.hibernate.core)
implementation(libs.mongo.java.driver.sync)
}

// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}

Expand Down
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ errorprone = "4.0.1"
google-errorprone-core = "2.9.0"
nullaway = "0.10.26"
jspecify = "0.3.0"
hibernate-core = "6.6.1.Final"
mongo-java-driver-sync = "5.2.0"

[libraries]
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
nullaway = { module = "com.uber.nullaway:nullaway", version.ref = "nullaway" }
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
google-errorprone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "google-errorprone-core" }
hibernate-core = { module = "org.hibernate.orm:hibernate-core", version.ref = "hibernate-core" }
mongo-java-driver-sync = { module = "org.mongodb:mongodb-driver-sync", version.ref = "mongo-java-driver-sync" }

[plugins]
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
Expand Down
49 changes: 49 additions & 0 deletions src/main/java/com/mongodb/hibernate/cfg/ConfigurationHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2024-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.hibernate.cfg;

import static org.hibernate.internal.util.NullnessUtil.castNonNull;

import com.mongodb.hibernate.exception.ConfigurationException;
import java.util.Map;
import org.jspecify.annotations.Nullable;

/** Collection of helper methods for dealing with configuration settings. */
public final class ConfigurationHelper {

private ConfigurationHelper() {}

public static String getRequiredConfiguration(Map<String, Object> configurationValues, String property) {
return castNonNull(doGetConfiguration(configurationValues, property, true));
}

public static @Nullable String getOptionalConfiguration(Map<String, Object> configurationValues, String property) {
return doGetConfiguration(configurationValues, property, false);
}

private static @Nullable String doGetConfiguration(
Map<String, Object> configurationValues, String property, boolean required) {
var configuration = configurationValues.get(property);
if (configuration == null && required) {
throw new ConfigurationException(property, "value required");
}
if (configuration != null && !(configuration instanceof String)) {
throw new ConfigurationException(property, "value is not of string type");
}
return (String) configuration;
}
}
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved
40 changes: 40 additions & 0 deletions src/main/java/com/mongodb/hibernate/dialect/MongoDialect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2024-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.hibernate.dialect;

import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;

public class MongoDialect extends Dialect {
jyemin marked this conversation as resolved.
Show resolved Hide resolved
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved
public static final int MINIMUM_MONGODB_MAJOR_VERSION_SUPPORTED = 6;

private static final DatabaseVersion MINIMUM_VERSION =
DatabaseVersion.make(MINIMUM_MONGODB_MAJOR_VERSION_SUPPORTED);

public MongoDialect() {
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
this(MINIMUM_VERSION);
}

public MongoDialect(final DatabaseVersion version) {
super(version);
}

public MongoDialect(final DialectResolutionInfo dialectResolutionInfo) {
super(dialectResolutionInfo);
}
}
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2024-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.hibernate.exception;
jyemin marked this conversation as resolved.
Show resolved Hide resolved

public class ConfigurationException extends RuntimeException {
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved

private final String property;

public ConfigurationException(String property, String message) {
super(message);
this.property = property;
}

public ConfigurationException(String property, String message, Throwable cause) {
super(message, cause);
this.property = property;
}

@Override
public String getMessage() {
return String.format("Invalid '%s' configuration: %s", property, super.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2024-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.hibernate.exception;

public class NotYetImplementedException extends RuntimeException {
jyemin marked this conversation as resolved.
Show resolved Hide resolved

public NotYetImplementedException() {}

public NotYetImplementedException(String message) {
super(message);
}
}
149 changes: 149 additions & 0 deletions src/main/java/com/mongodb/hibernate/jdbc/MongoConnectionProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright 2024-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.hibernate.jdbc;

import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;
import static org.hibernate.cfg.JdbcSettings.*;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate of #8 (comment): let's not use * imports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

solved. Palantir won't complain so I think it is minor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I learn about Palantir, the more inferior it seems to SpotBugs:

  • it's not configurable, so we can't enforce classes being final by default, for example;
  • it does not consider obviously bad practices like * imports to be bad (they may cause unnecessary name conflicts, and much worse, they may result in new name conflicts when upgrading dependencies, even when no code changes were made).

I wonder why was it chosen over SpotBugs.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't Palantir just a formatter, more akin to Checkstyle than SpotBugs?

ErrorProne is the thing that is closer to Spotbugs, and I believe it is extensible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am just judging based on what Nathan communicated at a catchup meeting: we can't configure the linter, at least, not nearly as flexibly as SpotBugs. If it is not Palantir but ErrorProne who does the checking, then my comment was about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't quite know much about linter so my remarks in the meeting might be wrong.
I think Palantir belongs to Spotless ecosystem. From its doc, since v7 (currently still in BETA), it does support something you desire: https://github.com/diffplug/spotless/tree/main/plugin-gradle#linting
Also, the decision to put @Nullable into the same line was from Spotless as well:
https://github.com/diffplug/spotless/tree/main/plugin-gradle#formatannotations

import static org.hibernate.internal.util.NullnessUtil.castNonNull;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.hibernate.cfg.ConfigurationHelper;
import com.mongodb.hibernate.exception.ConfigurationException;
import com.mongodb.hibernate.exception.NotYetImplementedException;
import java.sql.Connection;
import java.util.Map;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.service.spi.Configurable;
import org.hibernate.service.spi.Startable;
import org.hibernate.service.spi.Stoppable;
import org.jspecify.annotations.Nullable;

/**
* MongoDB dialect's customized JDBC {@link ConnectionProvider} spi implementation, whose class name is supposed to be
jyemin marked this conversation as resolved.
Show resolved Hide resolved
* provided as the following Hibernate property to kick off MongoDB dialect's JDBC flow:
*
* <ul>
* <li>hibernate.connection.provider_class
* </ul>
*
* <p>The following Hibernate JDBC properties will be relied upon by Hibernate's {@link Configurable} spi mechanism:
*
* <ul>
* <li>jakarta.persistence.jdbc.url
* <li>jakarta.persistence.jdbc.user
* <li>jakarta.persistence.jdbc.password
* </ul>
*
* <p><code>jakarta.persistence.jdbc.url</code> property is mandatory and it maps to MongoDB's {@link ConnectionString},
jyemin marked this conversation as resolved.
Show resolved Hide resolved
* in which database name must be provided to align with JDBC URL's convention. The other two JDBC properties are
* optional.
*
* @see ConnectionProvider
* @see Configurable
*/
public class MongoConnectionProvider implements ConnectionProvider, Configurable, Startable, Stoppable {

// non-null after configure(Map<String, Object>) method is invoked successfully
private @Nullable ConnectionString connectionString;
private @Nullable String database;

// non-null after start() method is invoked successfully
private @Nullable MongoClient mongoClient;
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
stIncMale marked this conversation as resolved.
Show resolved Hide resolved

private @Nullable String user;
private @Nullable String password;

@Override
public Connection getConnection() {
throw new NotYetImplementedException();
}

@Override
public void closeConnection(Connection connection) {
throw new NotYetImplementedException();
}

@Override
public boolean supportsAggressiveRelease() {
return false; // won't be used in container
}

@Override
public boolean isUnwrappableAs(Class<?> unwrapType) {
jyemin marked this conversation as resolved.
Show resolved Hide resolved
return ConnectionProvider.class.equals(unwrapType)
|| MongoConnectionProvider.class.isAssignableFrom(unwrapType);
}

@Override
public <T> T unwrap(Class<T> unwrapType) {
if (isUnwrappableAs(unwrapType)) {
return unwrapType.cast(this);
} else {
throw new UnknownUnwrapTypeException(unwrapType);
}
}

@Override
public void configure(Map<String, Object> configurationValues) {
var jdbcUrl = ConfigurationHelper.getRequiredConfiguration(configurationValues, JAKARTA_JDBC_URL);
try {
this.connectionString = new ConnectionString(jdbcUrl);
} catch (IllegalArgumentException iae) {
throw new ConfigurationException(JAKARTA_JDBC_URL, "invalid MongoDB connection string", iae);
}
var database = this.connectionString.getDatabase();
if (database == null) {
throw new ConfigurationException(JAKARTA_JDBC_URL, "database must be provided");
}
this.database = database;
this.user = ConfigurationHelper.getOptionalConfiguration(configurationValues, JAKARTA_JDBC_USER);
this.password = ConfigurationHelper.getOptionalConfiguration(configurationValues, JAKARTA_JDBC_PASSWORD);
}

@Override
public void start() {
// connectionString and database are set as mandatory values in the above configure method
// if either is unset, exception would have been thrown and this method invocation would have be skipped
castNonNull(this.connectionString);
castNonNull(this.database);

var clientSettingsBuilder = MongoClientSettings.builder().applyConnectionString(connectionString);
if (this.user != null) {
var password = this.password == null ? null : this.password.toCharArray();
var credential = MongoCredential.createCredential(this.user, this.database, password);
clientSettingsBuilder.credential(credential);
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved
}

var codecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry());
jyemin marked this conversation as resolved.
Show resolved Hide resolved
clientSettingsBuilder.codecRegistry(codecRegistry);
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved

var clientSettings = clientSettingsBuilder.build();
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved
jyemin marked this conversation as resolved.
Show resolved Hide resolved
this.mongoClient = MongoClients.create(clientSettings);
}

@Override
public void stop() {
if (this.mongoClient != null) {
this.mongoClient.close();
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/mongodb/hibernate/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NullMarked
jyemin marked this conversation as resolved.
Show resolved Hide resolved
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
package com.mongodb.hibernate;

import org.jspecify.annotations.NullMarked;
Loading