Skip to content

Commit

Permalink
feat: Liquibase use mongo client
Browse files Browse the repository at this point in the history
feat: Try to use a different way
  • Loading branch information
Thomas Canava authored and Malandril committed Feb 18, 2025
1 parent c190b10 commit 05501ee
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 59 deletions.
5 changes: 3 additions & 2 deletions docs/src/main/asciidoc/liquibase-mongodb.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ The following is an example for the `{config-file}` file:
[source,properties]
----
# configure MongoDB
quarkus.mongodb.connection-string = mongodb://localhost:27017
quarkus.mongodb.connection-string = mongodb://localhost:27017/mydatabase
# Liquibase MongoDB minimal config properties
quarkus.liquibase-mongodb.migrate-at-start=true
Expand All @@ -80,8 +80,9 @@ quarkus.liquibase-mongodb.migrate-at-start=true
# quarkus.liquibase-mongodb.default-catalog-name=DefaultCatalog
# quarkus.liquibase-mongodb.default-schema-name=DefaultSchema
----
NOTE: Liquibase needs a database either in the connection string or with the `quarkus.mongodb.database` property.

NOTE: Liquibase MongoDB is configured using a connection string, we do our best to craft a connection string that matches the MongoDB client configuration but if some configuration properties are not working you may consider adding them directly into the `quarkus.mongodb.connection-string` config property.
NOTE: By default, Liquibase MongoDB is configured to use the default MongoDB client you can change the client with `quarkus.liquibase-mongodb.mongodb-client`.

Add a changeLog file to the default folder following the Liquibase naming conventions: `{change-log}`
YAML, JSON and XML formats are supported for the changeLog.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,45 @@
import java.io.FileNotFoundException;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.mongodb.client.MongoClient;

import io.quarkus.arc.Arc;
import io.quarkus.liquibase.mongodb.runtime.LiquibaseMongodbBuildTimeConfig;
import io.quarkus.liquibase.mongodb.runtime.LiquibaseMongodbConfig;
import io.quarkus.mongodb.runtime.MongoClientBeanUtil;
import io.quarkus.mongodb.runtime.MongoClientConfig;
import io.quarkus.mongodb.runtime.MongoClients;
import io.quarkus.mongodb.runtime.MongodbConfig;
import io.quarkus.runtime.util.StringUtil;
import liquibase.Contexts;
import liquibase.LabelExpression;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.ext.mongodb.database.MongoConnection;
import liquibase.ext.mongodb.database.MongoLiquibaseDatabase;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.CompositeResourceAccessor;
import liquibase.resource.DirectoryResourceAccessor;
import liquibase.resource.ResourceAccessor;

public class LiquibaseMongodbFactory {

private final MongoClientConfig mongoClientConfig;
private final LiquibaseMongodbConfig liquibaseMongodbConfig;
private final LiquibaseMongodbBuildTimeConfig liquibaseMongodbBuildTimeConfig;

//connection-string format, see https://docs.mongodb.com/manual/reference/connection-string/
Pattern HAS_DB = Pattern
private static final Pattern HAS_DB = Pattern
.compile("(?<prefix>mongodb://|mongodb\\+srv://)(?<hosts>[^/]*)(?<slash>[/]?)(?<db>[^?]*)(?<options>\\??.*)");
private final LiquibaseMongodbConfig liquibaseMongodbConfig;
private final LiquibaseMongodbBuildTimeConfig liquibaseMongodbBuildTimeConfig;
private final MongodbConfig mongodbConfig;

public LiquibaseMongodbFactory(LiquibaseMongodbConfig config,
LiquibaseMongodbBuildTimeConfig liquibaseMongodbBuildTimeConfig, MongoClientConfig mongoClientConfig) {
LiquibaseMongodbBuildTimeConfig liquibaseMongodbBuildTimeConfig, MongodbConfig mongodbConfig) {
this.liquibaseMongodbConfig = config;
this.liquibaseMongodbBuildTimeConfig = liquibaseMongodbBuildTimeConfig;
this.mongoClientConfig = mongoClientConfig;
this.mongodbConfig = mongodbConfig;
}

private ResourceAccessor resolveResourceAccessor() throws FileNotFoundException {
Expand Down Expand Up @@ -83,54 +89,46 @@ private String parseChangeLog(String changeLog) {

public Liquibase createLiquibase() {
try (ResourceAccessor resourceAccessor = resolveResourceAccessor()) {
MongoClients clients = Arc.container().select(MongoClients.class).stream().findFirst().orElseThrow();
String mongoClientName;
MongoClientConfig mongoClientConfig;
if (liquibaseMongodbConfig.mongodbClient().isPresent()) {
mongoClientName = liquibaseMongodbConfig.mongodbClient().get();
mongoClientConfig = mongodbConfig.mongoClientConfigs().get(mongoClientName);
if (mongoClientConfig == null) {
throw new IllegalArgumentException("Mongo client named '%s' not found".formatted(mongoClientName));
}
} else {
mongoClientConfig = mongodbConfig.defaultMongoClientConfig();
mongoClientName = MongoClientBeanUtil.DEFAULT_MONGOCLIENT_NAME;
}
String parsedChangeLog = parseChangeLog(liquibaseMongodbBuildTimeConfig.changeLog());
String connectionString = this.mongoClientConfig.connectionString().orElse("mongodb://localhost:27017");

// Every MongoDB client configuration must be added to the connection string, we didn't add all as it would be too much to support.
// For reference, all connections string options can be found here: https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-options.

String connectionString = mongoClientConfig.connectionString().orElse("mongodb://localhost:27017");
Matcher matcher = HAS_DB.matcher(connectionString);
if (!matcher.matches() || matcher.group("db") == null || matcher.group("db").isEmpty()) {
connectionString = matcher.replaceFirst(
"${prefix}${hosts}/"
+ this.mongoClientConfig.database()
.orElseThrow(() -> new IllegalArgumentException("Config property " +
"'quarkus.mongodb.database' must be defined when no database exist in the connection string"))
+ "${options}");
Optional<String> maybeDatabase = mongoClientConfig.database();
if (maybeDatabase.isEmpty()) {
if (matcher.matches() && !StringUtil.isNullOrEmpty(matcher.group("db"))) {
maybeDatabase = Optional.of(matcher.group("db"));
} else {
throw new IllegalArgumentException("Config property 'quarkus.mongodb.database' must " +
"be defined when no database exist in the connection string");
}
}
if (mongoClientConfig.credentials().authSource().isPresent()) {
boolean alreadyHasQueryParams = connectionString.contains("?");
connectionString += (alreadyHasQueryParams ? "&" : "?") + "authSource="
+ mongoClientConfig.credentials().authSource().get();
Database database = createDatabase(clients, mongoClientName, maybeDatabase.get());
if (liquibaseMongodbConfig.liquibaseCatalogName().isPresent()) {
database.setLiquibaseCatalogName(liquibaseMongodbConfig.liquibaseCatalogName().get());
}
if (mongoClientConfig.credentials().authMechanism().isPresent()) {
boolean alreadyHasQueryParams = connectionString.contains("?");
connectionString += (alreadyHasQueryParams ? "&" : "?") + "authMechanism="
+ mongoClientConfig.credentials().authMechanism().get();
if (liquibaseMongodbConfig.liquibaseSchemaName().isPresent()) {
database.setLiquibaseSchemaName(liquibaseMongodbConfig.liquibaseSchemaName().get());
}
if (!mongoClientConfig.credentials().authMechanismProperties().isEmpty()) {
boolean alreadyHasQueryParams = connectionString.contains("?");
connectionString += (alreadyHasQueryParams ? "&" : "?") + "authMechanismProperties="
+ mongoClientConfig.credentials().authMechanismProperties().entrySet().stream()
.map(prop -> prop.getKey() + ":" + prop.getValue()).collect(Collectors.joining(","));
if (liquibaseMongodbConfig.liquibaseTablespaceName().isPresent()) {
database.setLiquibaseTablespaceName(liquibaseMongodbConfig.liquibaseTablespaceName().get());
}

Database database = DatabaseFactory.getInstance().openDatabase(connectionString,
this.mongoClientConfig.credentials().username().orElse(null),
this.mongoClientConfig.credentials().password().orElse(null),
null, resourceAccessor);

if (database != null) {
liquibaseMongodbConfig.liquibaseCatalogName().ifPresent(database::setLiquibaseCatalogName);
liquibaseMongodbConfig.liquibaseSchemaName().ifPresent(database::setLiquibaseSchemaName);
liquibaseMongodbConfig.liquibaseTablespaceName().ifPresent(database::setLiquibaseTablespaceName);

if (liquibaseMongodbConfig.defaultCatalogName().isPresent()) {
database.setDefaultCatalogName(liquibaseMongodbConfig.defaultCatalogName().get());
}
if (liquibaseMongodbConfig.defaultSchemaName().isPresent()) {
database.setDefaultSchemaName(liquibaseMongodbConfig.defaultSchemaName().get());
}
if (liquibaseMongodbConfig.defaultCatalogName().isPresent()) {
database.setDefaultCatalogName(liquibaseMongodbConfig.defaultCatalogName().get());
}
if (liquibaseMongodbConfig.defaultSchemaName().isPresent()) {
database.setDefaultSchemaName(liquibaseMongodbConfig.defaultSchemaName().get());
}
Liquibase liquibase = new Liquibase(parsedChangeLog, resourceAccessor, database);

Expand All @@ -145,6 +143,16 @@ public Liquibase createLiquibase() {
}
}

private Database createDatabase(MongoClients clients, String clientName, String databaseName) {
MongoConnection databaseConnection = new MongoConnection();
MongoClient mongoClient = clients.createMongoClient(clientName);
databaseConnection.setMongoClient(mongoClient);
databaseConnection.setMongoDatabase(mongoClient.getDatabase(databaseName));
Database database = new MongoLiquibaseDatabase();
database.setConnection(databaseConnection);
return database;
}

public LiquibaseMongodbConfig getConfiguration() {
return liquibaseMongodbConfig;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ public interface LiquibaseMongodbConfig {
@WithDefault("true")
boolean enabled();

/**
* Mongodb client name to use to connect to database, defaults to the default mongodb client.
*/
Optional<String> mongodbClient();

/**
* The migrate at start flag
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public Supplier<LiquibaseMongodbFactory> liquibaseSupplier(LiquibaseMongodbConfi
return new Supplier<LiquibaseMongodbFactory>() {
@Override
public LiquibaseMongodbFactory get() {
return new LiquibaseMongodbFactory(config, buildTimeConfig, mongodbConfig.defaultMongoClientConfig());
return new LiquibaseMongodbFactory(config, buildTimeConfig, mongodbConfig);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.quarkus.it.liquibase.mongodb;

import io.quarkus.mongodb.panache.PanacheMongoEntity;
import io.quarkus.mongodb.panache.common.MongoEntity;

@MongoEntity(database = "fruit-client")
public class Fruit extends PanacheMongoEntity {
public String name;
public String color;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
quarkus.mongodb.connection-string=mongodb://localhost:27017
quarkus.mongodb.database=fruits
quarkus.mongodb.fruit-client.connection-string=mongodb://localhost:27018
quarkus.mongodb.fruit-client.database=fruits
quarkus.liquibase-mongodb.change-log=liquibase/changelog.xml
quarkus.liquibase-mongodb.migrate-at-start=true
quarkus.liquibase-mongodb.migrate-at-start=true
quarkus.liquibase-mongodb.mongodb-client=fruit-client
# The tests use flapdoodle no need for devservices
quarkus.mongodb.devservices.enabled=false
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@
import com.mongodb.client.MongoClient;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.common.ResourceArg;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.mongodb.MongoTestResource;
import io.restassured.common.mapper.TypeRef;

@QuarkusTest
@QuarkusTestResource(MongoTestResource.class)
@QuarkusTestResource(value = MongoTestResource.class, initArgs = @ResourceArg(name = "port", value = "27018"))
@DisabledOnOs(OS.WINDOWS)
class FruitResourceTest {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
import org.junit.jupiter.api.condition.OS;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.common.ResourceArg;
import io.quarkus.test.junit.QuarkusIntegrationTest;
import io.quarkus.test.mongodb.MongoTestResource;
import io.restassured.common.mapper.TypeRef;

@QuarkusIntegrationTest
@QuarkusTestResource(MongoTestResource.class)
@QuarkusTestResource(value = MongoTestResource.class, initArgs = @ResourceArg(name = "port", value = "27018"))
@DisabledOnOs(OS.WINDOWS)
class NativeFruitResourceTestIT {
@Test
Expand Down

0 comments on commit 05501ee

Please sign in to comment.