Skip to content

Commit

Permalink
implement ConnectionProvider (#8)
Browse files Browse the repository at this point in the history
* implement ConnectionProvider

* change minimal MongoDB version to 6

* Update src/test/resources/hibernate.properties

Co-authored-by: Jeff Yemin <[email protected]>

* remove package-info.java in test folder;
add copyright to package-info.java in main folder

* improve Javadoc for MongoConnectionProvider per review comments

* remove unnecessary codec setting stuff

* simplify the dummy implementation of the three methods irrelevant to our usage

* simplify MongoConnectionProvider per code review comments

* remove exception package and move ConfigurationException to cfg package

* add missing Javadocs for MongoDialect

* add MQL acronym expansion in Javadoc

* round #2 code review comments

* change mininum mongodb version to v6.3

* use HibernateException instead of ConfigurationException.java

* switch MongoDialect minimum db version to 6.0

* Update src/main/java/com/mongodb/hibernate/jdbc/MongoConnectionProvider.java

Co-authored-by: Jeff Yemin <[email protected]>

* deleted unused constructors in MongoDialect

* add Javadoc for the root package-info.java

* make more exception messages begin with uppercase

* update various gradle plugin to latest versions

* add sl4j logging dependencies and logback-test.xml config

* fix a static check issue

* change default constructor of MongoDialect to end up with null value; override getMinimumSupportedVersion()

* some minor improvements

* change as per Valentin's excellent code review comments

* update SessionFactoryTests as per code review comments

* update MongoDialect as per code review comments

* update MongoConnectionProvider as per code review comments

* delete .editorconfig file and move it to another separate PR

* fix a static check style issue

* resolve some code review comments

* fix style checking issue

* minor changes to improve Dialect min version usage

* improve mentioning of MongoDB Java Driver in MongoDialect's Javadoc

* add transient keyword to MongoClient in MongoConnectionProvider

* Update src/main/java/com/mongodb/hibernate/jdbc/MongoConnectionProvider.java

Co-authored-by: Valentin Kovalenko <[email protected]>

* change as per code review comments

* remove database checking logic

* fix import style issue

* change per some code review comments

* add missing 'compileJava' gradle task in evergreen's static-check.sh

---------

Co-authored-by: Jeff Yemin <[email protected]>
Co-authored-by: Valentin Kovalenko <[email protected]>
  • Loading branch information
3 people authored Nov 14, 2024
1 parent 163601d commit caf4001
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .evergreen/static-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ source java-config.sh
echo "mongo-hibernate: static checking ..."

./gradlew -version
./gradlew -PxmlReports.enabled=true --info -x test clean check
./gradlew -PxmlReports.enabled=true --info -x test clean check compileJava
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
52 changes: 52 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,52 @@
/*
* 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;

/**
* A MongoDB {@link Dialect} for {@linkplain #getMinimumSupportedVersion() version 6.0 and above}.
*
* <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), but it is still possible to integrate with Hibernate by
* creating a JDBC adaptor on top of <a href="https://www.mongodb.com/docs/drivers/java/sync/current/">MongoDB Java
* Driver</a>.
*/
public final class MongoDialect extends Dialect {
private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make(6);

/** Default constructor used when no version info is available. */
public MongoDialect() {
super((DatabaseVersion) null);
}

/**
* Constructor used when MongoDB meta data is available.
*
* @param info MongoDB meta data
*/
public MongoDialect(DialectResolutionInfo info) {
super(info);
}

@Override
protected DatabaseVersion getMinimumSupportedVersion() {
return MINIMUM_VERSION;
}
}
20 changes: 20 additions & 0 deletions src/main/java/com/mongodb/hibernate/dialect/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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.
*/

@NullMarked
package com.mongodb.hibernate.dialect;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.internal;

import java.io.Serial;

/**
* 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.
*
* <p>This class is not part of the public API and may be removed or changed at any time.
*/
public final class NotYetImplementedException extends RuntimeException {

@Serial
private static final long serialVersionUID = 1L;

/**
* Default constructor.
*
* <p>It is recommended to use the other constructor with some explanation.
*/
public NotYetImplementedException() {}

/**
* Constructor with message parameter.
*
* @param message explanation on when the feature is to be implemented
*/
public NotYetImplementedException(String message) {
super(message);
}
}
20 changes: 20 additions & 0 deletions src/main/java/com/mongodb/hibernate/internal/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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.
*/

@NullMarked
package com.mongodb.hibernate.internal;

import org.jspecify.annotations.NullMarked;
153 changes: 153 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,153 @@
/*
* 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.JAKARTA_JDBC_PASSWORD;
import static org.hibernate.cfg.JdbcSettings.JAKARTA_JDBC_URL;
import static org.hibernate.cfg.JdbcSettings.JAKARTA_JDBC_USER;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.hibernate.internal.NotYetImplementedException;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectOutputStream;
import java.io.Serial;
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;

/**
* {@linkplain com.mongodb.hibernate.dialect.MongoDialect MongoDB dialect}'s customized JDBC {@link ConnectionProvider}
* SPI implementation.
*
* <p>{@link MongoConnectionProvider} uses the following Hibernate properties:
*
* <table>
* <tr><th>Property</th><th>Description</th><th>Required</th></tr>
* <tr>
* <td>{@value JdbcSettings#JAKARTA_JDBC_URL}</td>
* <td>MongoDB
* <a href="https://www.mongodb.com/docs/manual/reference/connection-string/">connection string</a>,
* which must specify the database name for authentication
* if {@value JdbcSettings#JAKARTA_JDBC_USER} is specified.</td>
* <td>✓</td>
* </tr>
* <tr>
* <td>{@value JdbcSettings#JAKARTA_JDBC_USER}</td>
* <td>{@code userName} for {@link com.mongodb.MongoCredential#createCredential(String, String, char[])}</td>
* <td></td>
* </tr>
* <tr>
* <td>{@value JdbcSettings#JAKARTA_JDBC_PASSWORD}</td>
* <td>{@code password} for {@link com.mongodb.MongoCredential#createCredential(String, String, char[])}</td>
* <td></td>
* </tr>
* </table>
*
* @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 final class MongoConnectionProvider implements ConnectionProvider, Configurable, Stoppable {

@Serial
private static final long serialVersionUID = 1L;

private @Nullable MongoClient mongoClient;

@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) {
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 (RuntimeException e) {
throw new HibernateException(
String.format(
"Failed to create ConnectionString from configuration [%s] with value [%s]",
JAKARTA_JDBC_URL, jdbcUrl),
e);
}

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();
this.mongoClient = MongoClients.create(clientSettings);
}

@Override
public void stop() {
if (this.mongoClient != null) {
this.mongoClient.close();
}
}

@Serial
private void writeObject(ObjectOutputStream out) throws IOException {
throw new NotSerializableException(
"This class is not designed to be serialized despite it having to implement `Serializable`");
}
}
Loading

0 comments on commit caf4001

Please sign in to comment.