
org.nuiton.topia.service.migration.TopiaMigrationService Maven / Gradle / Ivy
Show all versions of topia-extension-migration Show documentation
package org.nuiton.topia.service.migration;
/*-
* #%L
* ObServe Toolkit :: ToPIA Migration service
* %%
* Copyright (C) 2017 - 2018 IRD, Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* .
* #L%
*/
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.resource.transaction.spi.TransactionStatus;
import org.nuiton.topia.persistence.TopiaApplicationContext;
import org.nuiton.topia.persistence.TopiaMigrationServiceException;
import org.nuiton.topia.persistence.internal.support.HibernateTopiaSqlSupport;
import org.nuiton.topia.persistence.support.TopiaSqlSupport;
import org.nuiton.topia.persistence.script.SqlScriptConsumer;
import org.nuiton.topia.service.migration.resources.MigrationVersionResource;
import org.nuiton.util.TimeLog;
import org.nuiton.version.Version;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* Topia migration service default implementation.
*
* This new version of the migration service requires no configuration by default.
*
* The only thing you ca, configure is the migration user callback (see @link {@link TopiaMigrationServiceAskUserToMigrate}.
*
* Created by tchemit on 05/05/2018.
*
* @author Tony Chemit - [email protected]
*/
@SuppressWarnings("WeakerAccess")
public class TopiaMigrationService implements org.nuiton.topia.persistence.TopiaMigrationService {
private static final TimeLog TIME_LOG = new TimeLog(TopiaMigrationService.class, 100, 1000);
private static final Log log = LogFactory.getLog(TopiaMigrationService.class);
/**
* Configuration of service.
*/
protected TopiaMigrationServiceConfiguration configuration;
/**
* Contains all states of the service.
*/
protected TopiaMigrationServiceContext context;
@Override
public String getSchemaVersion() throws TopiaMigrationServiceException {
return getContext().getDbVersion().getVersion();
}
@Override
public void initOnCreateSchema() throws TopiaMigrationServiceException {
// Schema has just been created, save the application version
getContext().createSchemaIfNotExist();
getContext().saveModelVersion();
}
@Override
public void runSchemaMigration() throws TopiaMigrationServiceException {
TopiaMigrationServiceContext context = getContext();
Version modelVersion = context.getModelVersion();
Version dbVersion = context.getDbVersion();
boolean dbNotVersioned = context.isDbNotVersioned();
boolean versionTableExist = context.isVersionTableExist();
log.info(String.format("Starting Topia Migration Service - Model version : %s, Database version : %s", modelVersion, dbVersion));
log.debug(String.format("Is db not versioned ? = %s", dbNotVersioned));
log.debug(String.format("TMSVersion exists = %s", versionTableExist));
if (!versionTableExist) {
context.createSchemaIfNotExist();
}
if (versionTableExist && dbVersion.equals(modelVersion)) {
log.info("Database is up to date, no migration needed.");
return;
}
if (versionTableExist && dbNotVersioned) {
log.info("Database is empty, no migration needed.");
context.saveModelVersion();
return;
}
List allVersions = context.getResources().getAvailableVersions();
log.info(String.format("Available versions: %1$s", allVersions));
List versionsToApply = context.getResources().getVersionsAfter(dbVersion);
if (versionsToApply.isEmpty()) {
log.info("No version to apply, no migration needed.");
context.saveModelVersion();
return;
}
log.info(String.format("Versions to apply: %1$s", versionsToApply));
Optional askUserToMigrate = context.getAskUserToMigrate();
// ask to perform the migration
boolean performMigration = askUserToMigrate.map(c -> c.canIMigrate(dbVersion, versionsToApply)).orElse(true);
log.debug("Handler choose : " + performMigration);
if (!performMigration) {
// user cancel migration
return;
}
long statementCount = 0;
for (Version version : versionsToApply) {
long t0 = TimeLog.getTime();
long versionCount = migrateVersion(context, version);
statementCount += versionCount;
TIME_LOG.log(t0, "migrationVersion", version.toString() + " - " + versionCount + " sql statements");
}
log.info(String.format("Ends migration - db version: %s - consume %d sql statement(s)", context.getDbVersion(), statementCount));
}
@Override
public void initTopiaService(TopiaApplicationContext topiaApplicationContext, Map serviceConfiguration) {
this.configuration = TopiaMigrationServiceConfiguration.of(Objects.requireNonNull(topiaApplicationContext));
}
@Override
public void close() {
context = null;
}
protected long migrateVersion(TopiaMigrationServiceContext context, Version version) {
MigrationVersionResource resource = context.getResource(version);
try (SessionFactory sessionFactory = context.newSessionFactory()) {
try (Session session = sessionFactory.openSession()) {
session.getTransaction().begin();
TopiaSqlSupport sqlSupport = new HibernateTopiaSqlSupport(session);
try (TopiaMigrationServiceExecutor executor = context.newExecutor(version, sqlSupport)) {
String logPrefix = executor.getLogPrefix();
log.info(logPrefix + "Start schema migration.");
resource.generateSqlScript(executor);
long inStatementCount = executor.flush();
log.info(logPrefix + String.format("Discover %d sql statement(s) to apply.", inStatementCount));
long outStatementCount;
try (SqlScriptConsumer sqlWork = SqlScriptConsumer.of(executor.getScriptForVersion())) {
sqlSupport.doSqlWork(sqlWork);
outStatementCount = sqlWork.getStatementCount();
log.info(logPrefix + String.format("Consume %d sql statement(s).", outStatementCount));
}
context.saveVersion(version);
session.getTransaction().commit();
return outStatementCount;
} catch (Exception e) {
// Exception, rollback transaction
log.error("Exception during schema migration on version: " + version + ", rollback transaction", e);
if (TransactionStatus.ACTIVE == session.getTransaction().getStatus()) {
session.getTransaction().rollback();
}
throw new TopiaMigrationServiceException("Exception during schema migration on version: " + version, e);
}
}
}
}
public void createSchemaIfNotExist() {
getContext().createSchemaIfNotExist();
}
public TopiaMigrationServiceContext getContext() {
return context == null ? context = TopiaMigrationServiceContext.of(configuration) : context;
}
}