![JAR search and dependency download from the Maven repository](/logo.png)
net.ozwolf.mongo.migrations.MongoTrek Maven / Gradle / Ivy
package net.ozwolf.mongo.migrations;
import com.mongodb.ConnectionString;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import net.ozwolf.mongo.migrations.exception.MongoTrekFailureException;
import net.ozwolf.mongo.migrations.internal.dao.DefaultSchemaVersionDAO;
import net.ozwolf.mongo.migrations.internal.dao.SchemaVersionDAO;
import net.ozwolf.mongo.migrations.internal.domain.Migration;
import net.ozwolf.mongo.migrations.internal.domain.MigrationCommands;
import net.ozwolf.mongo.migrations.internal.factory.MigrationCommandsFactory;
import net.ozwolf.mongo.migrations.internal.service.MigrationsService;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* Mongo Trek
*
* The mongoTrek main class allows an application to provide it's own {@code MongoDatabase} instance or MongoDB Connection string to then apply migrations to or report on the migration status of their database schema.
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class MongoTrek {
private final MongoClient mongo;
private final MongoDatabase database;
private final String migrationsFile;
private final boolean providedDatabase;
private SchemaVersionDAO schemaVersionDAO;
private MigrationsService migrationsServices;
private MigrationCommandsFactory commandsFactory;
private String schemaVersionCollection;
private final static Logger LOGGER = LoggerFactory.getLogger(MongoTrek.class);
private final static String DEFAULT_SCHEMA_VERSION_COLLECTION = "_schema_version";
private final static AtomicReference CLASS_LOADER = new AtomicReference<>(MongoTrek.class.getClassLoader());
/**
* Create a new MongoTrek instance that will connect to the provided connection string.
*
* @param migrationsFile The YAML or JSON file containing your MongoDB migrations.
* @param uri The Mongo instance connection string
* @see MongoDB Connection String
*/
public MongoTrek(String migrationsFile, String uri) {
this.migrationsFile = migrationsFile;
ConnectionString clientURI = new ConnectionString(uri);
if (clientURI.getDatabase() == null)
throw new IllegalArgumentException("URI [ " + uri + " ] must contain a database schema to connect to.");
this.mongo = MongoClients.create(clientURI);
this.database = this.mongo.getDatabase(clientURI.getDatabase());
this.providedDatabase = false;
this.schemaVersionCollection = DEFAULT_SCHEMA_VERSION_COLLECTION;
}
/**
* Create a new MongoTrek instance using a provided {@code MongoDatabase} instance. MongoTrek will not close this connection.
*
* @param migrationsFile The YAML or JSON file containing your MongoDB migrations.
* @param database The {@code MongoDatabase} instance.
*/
public MongoTrek(String migrationsFile, MongoDatabase database) {
this.migrationsFile = migrationsFile;
this.mongo = null;
this.database = database;
this.providedDatabase = true;
this.schemaVersionCollection = DEFAULT_SCHEMA_VERSION_COLLECTION;
}
/**
* Change the schema version collection from the default {@code _schema_version}
*
* @param collectionName The schema version collection name
*/
public void setSchemaVersionCollection(String collectionName) {
this.schemaVersionCollection = collectionName;
}
/**
* Migrate the Mongo database using the provided collection of commands. Will not apply versions already applied successfully.
*
* @return The trek state
* @throws MongoTrekFailureException If the migration fails for whatever reason.
*/
public MongoTrekState migrate() throws MongoTrekFailureException {
LOGGER.info("DATABASE MIGRATIONS");
MigrationCommands commands = commandsFactory().getCommands(migrationsFile, CLASS_LOADER.get());
commands.getSchemaVersionCollection().ifPresent(n -> {
if (schemaVersionCollection.equalsIgnoreCase(DEFAULT_SCHEMA_VERSION_COLLECTION))
schemaVersionCollection = n;
});
MongoTrekState state = migrationsService().getState(commands);
if (!commands.hasMigrations()) {
LOGGER.info(" No migrations to apply.");
return state;
}
Instant start = Instant.now();
AtomicInteger successfulCount = new AtomicInteger(0);
try {
MongoTrekState.Pending pending = state.getPending();
if (!pending.hasPendingMigrations()) {
LOGGER.info(" No migrations to apply.");
return state;
}
logStatus("migrate", state.getCurrentVersion());
LOGGER.info(String.format(" Applying : [ %s ] -> [ %s ]", pending.getNextPendingVersion(), pending.getLastPendingVersion()));
LOGGER.info(" Migrations :");
pending.getMigrations().forEach(m -> applyMigration(successfulCount, m));
// Get state after migrations have been applied.
return migrationsService().getState(commands);
} catch (Exception e) {
LOGGER.error("Error applying migration(s)", e);
throw new MongoTrekFailureException(e);
} finally {
Instant finish = Instant.now();
LOGGER.info(String.format(">>> [ %d ] migrations applied in [ %d seconds ] <<<", successfulCount.get(), Duration.between(start, finish).getSeconds()));
if (!this.providedDatabase) this.mongo.close();
}
}
/**
* Report the status of the migrations and provided commands. Does not apply the migrations.
*
* By default, this method will not log status to the logger. Refer to the {@link #status(boolean) status(boolean)} method.
*
* @return The trek state
* @throws MongoTrekFailureException If the status report fails for whatever reason.
*/
public MongoTrekState status() throws MongoTrekFailureException {
return status(false);
}
/**
* Report the status of the migrations and provided commands. Does not apply the migrations.
*
* @param logStatus flag indicating if the state should be logged
* @return The trek state
* @throws MongoTrekFailureException If the status report fails for whatever reason.
*/
public MongoTrekState status(boolean logStatus) throws MongoTrekFailureException {
if (logStatus) LOGGER.info("DATABASE MIGRATIONS");
MigrationCommands commands = commandsFactory().getCommands(migrationsFile, CLASS_LOADER.get());
MongoTrekState state = migrationsService().getState(commands);
try {
if (logStatus) {
logStatus("status", state.getCurrentVersion());
LOGGER.info(" Migrations :");
state.getMigrations().forEach(this::reportMigration);
}
return state;
} catch (Exception e) {
if (logStatus)
LOGGER.error("Error in commands and cannot provide status", e);
throw new MongoTrekFailureException(e);
} finally {
if (!this.providedDatabase) this.mongo.close();
}
}
/**
* Set the class loader for mongoTrek to use when loading migrations files from resource paths.
*
* By default, mongoTrek uses the class loader from {@code MongoTrek.class.getClassLoader()}, but sometimes this class loader is not relevant.
*
* @param classLoader the class loader for mongoTrek to use when loading resources
*/
public static void setClassLoader(ClassLoader classLoader) {
CLASS_LOADER.set(classLoader);
}
private void logStatus(String action, String currentVersion) {
LOGGER.info(String.format(" Database : [ %s ]", this.database.getName()));
LOGGER.info(String.format(" Schema Version : [ %s ]", schemaVersionCollection));
LOGGER.info(String.format(" Action : [ %s ]", action));
LOGGER.info(String.format("Current Version : [ %s ]", currentVersion));
}
private void applyMigration(AtomicInteger successfulCount, Migration migration) {
try {
LOGGER.info(String.format(" %s : %s", migration.getVersion(), migration.getDescription()));
schemaVersionDAO().save(migration.running());
Document result = migration.getCommand().migrate(this.database);
schemaVersionDAO().save(migration.successful(result));
successfulCount.incrementAndGet();
} catch (Exception e) {
schemaVersionDAO().save(migration.failed(e));
throw e;
}
}
private void reportMigration(Migration migration) {
LOGGER.info(String.format(" %s : %s", migration.getVersion(), migration.getDescription()));
LOGGER.info(String.format(" Tags: %s", migration.getTags()));
}
private MigrationsService migrationsService() {
if (migrationsServices == null)
migrationsServices = new MigrationsService(schemaVersionDAO());
return migrationsServices;
}
private SchemaVersionDAO schemaVersionDAO() {
if (schemaVersionDAO == null)
schemaVersionDAO = new DefaultSchemaVersionDAO(this.database.getCollection(schemaVersionCollection));
return schemaVersionDAO;
}
private MigrationCommandsFactory commandsFactory() {
if (commandsFactory == null)
commandsFactory = new MigrationCommandsFactory();
return commandsFactory;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy