Skip to content

Commit

Permalink
Support for DNS Seed List Connection Format or Atlas Cluster
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandru-slobodcicov committed Jan 24, 2021
1 parent a0be7d3 commit 3074ae3
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 48 deletions.
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
1. [Introduction](#introduction)
1. [Release Notes](#release-notes)
1. [Implemented Changes](#implemented-changes)
1. [Connection String Formats](#connection-string)
1. [Getting Started](#getting-started)
1. [Running tests](#running-tests)
1. [Integration](#integration)
Expand All @@ -28,9 +29,11 @@ Liquibase turned to be the most feasible tool to extend as it allows to define c
## Release Notes

#### 4.2.2.1
* Fixed [Issue-64:Support for DNS Seed List Connection Format or Atlas Cluster](https://github.com/liquibase/liquibase-mongodb/issues/66)
* Fixed [Issue-69: Does it support preconditions](https://github.com/liquibase/liquibase-mongodb/issues/69)
* Added DocumentExistsPrecondition, ExpectedDocumentCountPrecondition
* Fixed [Issue-74: createIndex with TTL (expireAfterSeconds) is ignored and normal index created](https://github.com/liquibase/liquibase-mongodb/issues/74)
* Fixed [Issue-79: CreateCollection silently drops supported options](https://github.com/liquibase/liquibase-mongodb/issues/79)

#### 4.2.2
* Support for Liquibase 4.2.2
Expand Down Expand Up @@ -101,6 +104,24 @@ Provides a helper to run specified database commands. This is the preferred meth
* [__adminCommand__](https://docs.mongodb.com/manual/reference/method/db.adminCommand/#db.adminCommand) -
Provides a helper to run specified database commands against the admin database

<a name="connection-string"></a>
## Connection String Formats

### [Standard Connection String Format](https://docs.mongodb.com/manual/reference/connection-string/index.html#standard-connection-string-format)

`
mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]
mongodb://mongodb1.example.com:27317,mongodb2.example.com:27017/?replicaSet=mySet&authSource=authDB
`

### [DNS Seed List Connection Format](https://docs.mongodb.com/manual/reference/connection-string/index.html#dns-seed-list-connection-format)

`
mongodb+srv://[username:password@]host[/[database][?options]]
mongodb+srv://server.example.com/
mongodb+srv://:@cluster0.example.com/testdb?authSource=$external&authMechanism=MONGODB-AWS
`

<a name="getting-started"></a>
## Getting Started

Expand All @@ -115,6 +136,10 @@ mongo-java-driver:3.12.7
### Installing

* Clone the project

```shell
git clone https://github.com/liquibase/liquibase-mongodb
```
* [Run tests](#running-tests)

<a name="running-tests"></a>
Expand All @@ -128,7 +153,7 @@ Run Integration tests by enabling `run-its` profile

### Run integration tests

```shell script
```shell
mvn clean install -Prun-its
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@
import com.mongodb.client.MongoClients;
import liquibase.Scope;
import liquibase.exception.DatabaseException;
import liquibase.util.StringUtil;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverPropertyInfo;
import java.util.Properties;
import java.util.logging.Logger;

import static java.util.Objects.nonNull;

public class MongoClientDriver implements Driver {

@Override
public Connection connect(String url, Properties info) {
public Connection connect(final String url, final Properties info) {
//Not applicable for non JDBC DBs
throw new UnsupportedOperationException("Cannot initiate a SQL Connection for a NoSql DB");
}
Expand All @@ -32,8 +35,9 @@ public MongoClient connect(final ConnectionString connectionString) throws Datab
}

@Override
public boolean acceptsURL(String url) {
return false;
public boolean acceptsURL(final String url) {
final String trimmedUrl = StringUtil.trimToEmpty(url);
return trimmedUrl.startsWith(MongoConnection.MONGO_DNS_PREFIX) || trimmedUrl.startsWith(MongoConnection.MONGO_PREFIX);
}

@Override
Expand Down
51 changes: 34 additions & 17 deletions src/main/java/liquibase/ext/mongodb/database/MongoConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,19 @@
import java.util.Optional;
import java.util.Properties;

import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.Optional.ofNullable;
import static liquibase.ext.mongodb.database.MongoLiquibaseDatabase.MONGODB_PRODUCT_SHORT_NAME;

@Getter
@Setter
@NoArgsConstructor
public class MongoConnection extends AbstractNoSqlConnection {

public static final int DEFAULT_PORT = 27017;
public static final String MONGO_PREFIX = MongoLiquibaseDatabase.MONGODB_PRODUCT_SHORT_NAME + "://";
public static final String MONGO_CONNECTION_STRING_PATTERN = "%s/%s";
public static final String MONGO_PREFIX = MONGODB_PRODUCT_SHORT_NAME + "://";
public static final String MONGO_DNS_PREFIX = MONGODB_PRODUCT_SHORT_NAME + "+srv://";

private ConnectionString connectionString;

Expand Down Expand Up @@ -80,24 +82,16 @@ public String getConnectionUserName() {
.map(MongoCredential::getUserName).orElse("");
}

@Override
public boolean isClosed() throws DatabaseException {
return isNull(client);
}

@Override
public void open(final String url, final Driver driverObject, final Properties driverProperties) throws DatabaseException {

try {

String urlWithCredentials = url;

if (nonNull(driverProperties)) {

final Optional<String> user = Optional.ofNullable(StringUtil.trimToNull(driverProperties.getProperty("user")));
final Optional<String> password = Optional.ofNullable(StringUtil.trimToNull(driverProperties.getProperty("password")));

if (user.isPresent() && password.isPresent()) {
// injects credentials
// mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database.collection][?options]]
urlWithCredentials = MONGO_PREFIX + user.get() + ":" + password.get() + "@" + StringUtil.trimToEmpty(url).replaceFirst(MONGO_PREFIX, "");
}
}
final String urlWithCredentials = injectCredentials(StringUtil.trimToEmpty(url), driverProperties);

this.connectionString = new ConnectionString(urlWithCredentials);

Expand All @@ -111,10 +105,33 @@ public void open(final String url, final Driver driverObject, final Properties d
}
}

private String injectCredentials(final String url, final Properties driverProperties) {

if (nonNull(driverProperties)) {

final Optional<String> user = Optional.ofNullable(StringUtil.trimToNull(driverProperties.getProperty("user")));
final Optional<String> password = Optional.ofNullable(StringUtil.trimToNull(driverProperties.getProperty("password")));

if (user.isPresent()) {
// injects credentials
// mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database.collection][?options]]
// mongodb+srv://[username:password@]host[:port1][?options]]
final String mongoPrefix = url.startsWith(MONGO_DNS_PREFIX) ? MONGO_DNS_PREFIX : MONGO_PREFIX;
return mongoPrefix + user.get() + password.map(p -> ":" + p).orElse("") + "@" +
url.substring(mongoPrefix.length());
}
}
return url;
}


@Override
public void close() throws DatabaseException {
try {
client.close();
if (!isClosed()) {
client.close();
client = null;
}
} catch (final Exception e) {
throw new DatabaseException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@
import liquibase.CatalogAndSchema;
import liquibase.Scope;
import liquibase.changelog.ChangeLogHistoryServiceFactory;
import liquibase.configuration.GlobalConfiguration;
import liquibase.configuration.LiquibaseConfiguration;
import liquibase.ext.mongodb.configuration.MongoConfiguration;
import liquibase.nosql.database.AbstractNoSqlDatabase;
import liquibase.exception.LiquibaseException;
import liquibase.executor.Executor;
import liquibase.executor.ExecutorService;
import liquibase.ext.mongodb.configuration.MongoConfiguration;
import liquibase.ext.mongodb.statement.DropAllCollectionsStatement;
import liquibase.nosql.database.AbstractNoSqlDatabase;
import lombok.NoArgsConstructor;
import lombok.Setter;

Expand All @@ -41,6 +40,7 @@ public class MongoLiquibaseDatabase extends AbstractNoSqlDatabase {

public static final String MONGODB_PRODUCT_NAME = "MongoDB";
public static final String MONGODB_PRODUCT_SHORT_NAME = "mongodb";
public static final String ADMIN_DATABSE_NAME = "admin";

@Setter
private Boolean adjustTrackingTablesOnStartup;
Expand All @@ -58,7 +58,7 @@ public void dropDatabaseObjects(final CatalogAndSchema schemaToDrop) throws Liqu

@Override
public String getDefaultDriver(final String url) {
if (url.startsWith(MongoConnection.MONGO_PREFIX)) {
if (url.startsWith(MongoConnection.MONGO_DNS_PREFIX) || url.startsWith(MongoConnection.MONGO_PREFIX)) {
return MongoClientDriver.class.getName();
}
return null;
Expand Down Expand Up @@ -90,7 +90,7 @@ protected String getDefaultDatabaseProductName() {

@Override
public String getSystemSchema() {
return "admin";
return ADMIN_DATABSE_NAME;
}

/*********************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import liquibase.database.Database;
import liquibase.database.DatabaseConnection;
import liquibase.exception.DatabaseException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
Expand Down Expand Up @@ -51,8 +50,6 @@ public void setAutoCommit(boolean autoCommit) throws DatabaseException {

@Override
public String nativeSQL(String sql) {
//TODO: Investigate whether can be thrown not applicable
//throw new UnsupportedOperationException();
return null;
}

Expand Down Expand Up @@ -86,9 +83,4 @@ public void rollback() throws DatabaseException {
// Do nothing
}

@Override
public boolean isClosed() throws DatabaseException {
return false;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package liquibase.ext.mongodb.database;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

@TestInstance(TestInstance.Lifecycle.PER_METHOD)
class MongoClientDriverTest {

protected MongoClientDriver mongoClientDriver;

@BeforeEach
void setUp() {
mongoClientDriver = new MongoClientDriver();
}

@Test
void acceptsURL() {
assertThat(mongoClientDriver.acceptsURL(null)).isFalse();
assertThat(mongoClientDriver.acceptsURL("")).isFalse();
assertThat(mongoClientDriver.acceptsURL("jdbc:oracle:thin:@localhost:1521:xe")).isFalse();
assertThat(mongoClientDriver.acceptsURL("mongodbsuffix://localhost")).isFalse();
assertThat(mongoClientDriver.acceptsURL("mongodb://localhost")).isTrue();
assertThat(mongoClientDriver.acceptsURL(" mongodb://localhost")).isTrue();
assertThat(mongoClientDriver.acceptsURL("mongodb+srv://localhost")).isTrue();
assertThat(mongoClientDriver.acceptsURL(" mongodb+srv://localhost")).isTrue();
}

@Test
void getMajorVersion() {
assertThat(mongoClientDriver.getMajorVersion()).isEqualTo(0);
}

@Test
void getMinorVersion() {
assertThat(mongoClientDriver.getMinorVersion()).isEqualTo(0);
}

@Test
void jdbcCompliant() {
assertThat(mongoClientDriver.jdbcCompliant()).isFalse();
}
}
Loading

0 comments on commit 3074ae3

Please sign in to comment.