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 24 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
442 changes: 442 additions & 0 deletions .editorconfig
stIncMale marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

15 changes: 7 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,38 @@
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)
testImplementation(libs.logback.classic)

testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly(libs.junit.platform.launcher)

errorprone(libs.nullaway)
api(libs.jspecify)

errorprone(libs.google.errorprone.core)

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

// 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 All @@ -57,8 +57,7 @@ tasks.named<Test>("test") {

spotless {
java {
// note: you can use an empty string for all the imports you didn't specify explicitly, '|' to join group without blank line, and '\\#` prefix for static imports
importOrder("java|javax", "", "\\#")
importOrder()

removeUnusedImports()

Expand Down
19 changes: 14 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,28 @@
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format

[versions]
junit-jupiter = "5.10.3"
junit-jupiter = "5.11.3"
spotless = "6.25.0"
palantir = "2.50.0"
errorprone = "4.0.1"
google-errorprone-core = "2.9.0"
nullaway = "0.10.26"
jspecify = "0.3.0"
errorprone = "4.1.0"
google-errorprone-core = "2.35.1"
nullaway = "0.12.1"
jspecify = "1.0.0"
hibernate-core = "6.6.1.Final"
mongo-java-driver-sync = "5.2.0"
slf4j-api = "2.0.16"
logback-classic = "1.5.12"

[libraries]
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" }
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" }
sl4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-classic" }

[plugins]
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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;

/**
* A temporary marker exception to denote that the feature in question is in the scope of MongoDB dialect but has not
* been implemented yet.
*
* <p>Ultimately all of its references should be eliminated sooner or later, and then this class is supposed to be
* deleted prior to product release.
*
* <p>It is recommended to provide some message to explain when it will be implemented (e.g. JIRA ticket id is a good
* idea), but that is optional.
*/
public class NotYetImplementedException extends RuntimeException {
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
stIncMale marked this conversation as resolved.
Show resolved Hide resolved

/**
* Default constructor.
*
* <p>It is recommended to use the other constructor with some explanation.
jyemin marked this conversation as resolved.
Show resolved Hide resolved
*/
public NotYetImplementedException() {}
jyemin marked this conversation as resolved.
Show resolved Hide resolved

/**
* Constructor with message parameter.
*
* @param message explanation on when the feature is to be implemented
*/
public NotYetImplementedException(String message) {
super(message);
}
}
60 changes: 60 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,60 @@
/*
* 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;

/**
* A MongoDB {@linkplain Dialect} for version 6.0 and above.
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
*
* <p>Usually Hibernate dialect represents some SQL RDBMS and speaks SQL with vendor-specific difference. MongoDB is a
* document DB and speaks <i>MQL</i> (MongoDB Query Language) in JSON format, but it is still possible to integrate with
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved
* Hibernate seamlessly by creating a JDBC adaptor on top of MongoDB's Java client library.
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
*
* <p>Some MongoDB-specific customization examples include:
*
* <ul>
* <li>MQL translation extension point
* <li>SQL {@linkplain java.sql.Types#ARRAY ARRAY} and {@linkplain java.sql.Types#STRUCT STRUCT} extension points to
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
* support MongoDB's embedding array and document
* <li>MQL parameterization customization
* </ul>
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
*/
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;
public static final int MINIMUM_MONGODB_MINOR_VERSION_SUPPORTED = 0;

private static final DatabaseVersion MINIMUM_VERSION =
DatabaseVersion.make(MINIMUM_MONGODB_MAJOR_VERSION_SUPPORTED, MINIMUM_MONGODB_MINOR_VERSION_SUPPORTED);
stIncMale marked this conversation as resolved.
Show resolved Hide resolved

/**
* Default constructor used when no version info is available.
*
* <p>Note that {@link Dialect} abstract class has two overloaded constructors accepting sole parameter, and only
* one accepts {@code null} value. Explicitly downcast to make the intention clear.
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
*/
public MongoDialect() {
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
super((DatabaseVersion) null);
}

/** {@inheritDoc} */
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
@Override
protected DatabaseVersion getMinimumSupportedVersion() {
return MINIMUM_VERSION;
}
}
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved
126 changes: 126 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,126 @@
/*
* 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.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 com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.hibernate.NotYetImplementedException;
import java.sql.Connection;
import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.cfg.JdbcSettings;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.service.spi.Configurable;
import org.hibernate.service.spi.Stoppable;
import org.jspecify.annotations.Nullable;

/**
* MongoDB dialect's customized JDBC {@link ConnectionProvider} SPI implementation, whose class name could be provided
NathanQingyangXu marked this conversation as resolved.
Show resolved Hide resolved
* using configuration property {@value org.hibernate.cfg.JdbcSettings#CONNECTION_PROVIDER}.
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
*
* <p>The following Hibernate JDBC properties will be relied upon by Hibernate's {@link Configurable} SPI mechanism:
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
*
* <ul>
* <li>{@linkplain org.hibernate.cfg.JdbcSettings#JAKARTA_JDBC_URL jakarta.persistence.jdbc.url}
* <li>{@linkplain org.hibernate.cfg.JdbcSettings#JAKARTA_JDBC_USER jakarta.persistence.jdbc.user}
* <li>{@linkplain org.hibernate.cfg.JdbcSettings#JAKARTA_JDBC_PASSWORD jakarta.persistence.jdbc.password}
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
* </ul>
*
* <p>{@value org.hibernate.cfg.JdbcSettings#JAKARTA_JDBC_URL} property is mandatory and it maps to MongoDB's <a
* href="https://www.mongodb.com/docs/manual/reference/connection-string/">connection string</a>, in which database name
* must be provided to align with JDBC URL's convention. The other two JDBC properties are optional.
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
*
* @see ConnectionProvider
* @see JdbcSettings#JAKARTA_JDBC_URL
* @see JdbcSettings#JAKARTA_JDBC_USER
* @see JdbcSettings#JAKARTA_JDBC_PASSWORD
* @see <a href="https://www.mongodb.com/docs/manual/reference/connection-string/">connection string</a>
*/
public class MongoConnectionProvider implements ConnectionProvider, Configurable, Stoppable {
stIncMale marked this conversation as resolved.
Show resolved Hide resolved

private @Nullable MongoClient mongoClient;
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
stIncMale marked this conversation as resolved.
Show resolved Hide resolved

@Override
public Connection getConnection() {
throw new NotYetImplementedException(
"To be implemented in scope of https://jira.mongodb.org/browse/HIBERNATE-29");
}

@Override
public void closeConnection(Connection connection) {
throw new NotYetImplementedException(
"To be implemented in scope of https://jira.mongodb.org/browse/HIBERNATE-29");
}

@Override
public boolean supportsAggressiveRelease() {
return false;
}

@Override
public boolean isUnwrappableAs(Class<?> unwrapType) {
jyemin marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

@Override
public <T> T unwrap(Class<T> unwrapType) {
throw new UnknownUnwrapTypeException(unwrapType);
}

@Override
public void configure(Map<String, Object> configValues) {
var jdbcUrl = configValues.get(JAKARTA_JDBC_URL);
if (jdbcUrl == null) {
throw new HibernateException("Configuration is required: " + JAKARTA_JDBC_URL);
}
if (!(jdbcUrl instanceof String)) {
throw new HibernateException(
String.format("Configuration ('%s') value ('%s') not of string type", JAKARTA_JDBC_URL, jdbcUrl));
}
ConnectionString connectionString;
try {
connectionString = new ConnectionString((String) jdbcUrl);
} catch (IllegalArgumentException iae) {
throw new HibernateException("Invalid MongoDB connection string: " + jdbcUrl, iae);
}
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
var database = connectionString.getDatabase();
if (database == null) {
throw new HibernateException("Database must be provided in connection string: " + jdbcUrl);
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
}

var clientSettingsBuilder = MongoClientSettings.builder().applyConnectionString(connectionString);

if (configValues.get(JAKARTA_JDBC_USER) != null || configValues.get(JAKARTA_JDBC_PASSWORD) != null) {
throw new NotYetImplementedException("To be implemented after auth could be tested in CI");
}

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();
}
}
}
32 changes: 32 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,32 @@
/*
* 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.
*/

/**
* The root package for MongoDB Dialect library, whose main sub-packages include:
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
*
* <dl>
* <dt>dialect
* <dd>MongoDB Dialect related classes
* <dt>jdbc
* <dd>various JDBC API implementation classes
* <dt>translate
* <dd>MQL translation (from Hibernate's SQL AST) classes
* </dl>
stIncMale marked this conversation as resolved.
Show resolved Hide resolved
*/
@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