Skip to content
Snippets Groups Projects
Commit edef8db0 authored by Robert Goldmann's avatar Robert Goldmann
Browse files

#663 - run migrator from BudgetMaster

parent 6c722bed
No related branches found
No related tags found
No related merge requests found
Pipeline #6292 passed
package de.deadlocker8.budgetmaster.migration;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
public class MigrationArguments
{
static class MigrationArgumentBuilder
{
private String sourceUrl;
private String destinationUrl;
private String destinationUsername;
private String destinationPassword;
public MigrationArgumentBuilder withSourceUrl(String databasePath)
{
databasePath = databasePath.replace("\\", "/");
this.sourceUrl = MessageFormat.format("jdbc:h2:/{0}", databasePath);
return this;
}
public MigrationArgumentBuilder withDestinationUrl(String hostname, Integer port, String databaseName)
{
this.destinationUrl = MessageFormat.format("jdbc:postgresql://{0}:{1,number,#}/{2}", hostname, port, databaseName);
return this;
}
public MigrationArgumentBuilder withDestinationCredentials(String username, String password)
{
this.destinationUsername = username;
this.destinationPassword = password;
return this;
}
public MigrationArguments build()
{
return new MigrationArguments(sourceUrl, destinationUrl, destinationUsername, destinationPassword);
}
}
private final String sourceUrl;
private final String destinationUrl;
private final String destinationUsername;
private final String destinationPassword;
private MigrationArguments(String sourceUrl, String destinationUrl, String destinationUsername, String destinationPassword)
{
this.sourceUrl = sourceUrl;
this.destinationUrl = destinationUrl;
this.destinationUsername = destinationUsername;
this.destinationPassword = destinationPassword;
}
public List<String> getArguments()
{
final ArrayList<String> arguments = new ArrayList<>();
arguments.add(MessageFormat.format("--spring.datasource.jdbc-url={0}", sourceUrl));
arguments.add(MessageFormat.format("--spring.seconddatasource.jdbc-url={0}", destinationUrl));
arguments.add(MessageFormat.format("--spring.seconddatasource.username={0}", destinationUsername));
arguments.add(MessageFormat.format("--spring.seconddatasource.password={0}", destinationPassword));
return arguments;
}
}
......@@ -5,12 +5,17 @@ import de.deadlocker8.budgetmaster.controller.BaseController;
import de.deadlocker8.budgetmaster.settings.SettingsService;
import de.deadlocker8.budgetmaster.utils.Mappings;
import de.deadlocker8.budgetmaster.utils.Strings;
import de.deadlocker8.budgetmaster.utils.WebRequestUtils;
import de.deadlocker8.budgetmaster.utils.notification.Notification;
import de.deadlocker8.budgetmaster.utils.notification.NotificationType;
import de.thecodelabs.utils.util.Localization;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.WebRequest;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
......@@ -31,12 +36,14 @@ public class MigrationController extends BaseController
public static final String MIGRATION_SETTINGS = "migration";
}
private final MigrationService migrationService;
private final SettingsService settingsService;
private final UserService userService;
@Autowired
public MigrationController(SettingsService settingsService, UserService userService)
public MigrationController(MigrationService migrationService, SettingsService settingsService, UserService userService)
{
this.migrationService = migrationService;
this.settingsService = settingsService;
this.userService = userService;
}
......@@ -56,7 +63,8 @@ public class MigrationController extends BaseController
}
@PostMapping
public String post(Model model,
public String post(WebRequest request,
Model model,
@ModelAttribute("MigrationSettings") @Valid MigrationSettings migrationSettings, BindingResult bindingResult,
@RequestParam(value = "verificationPassword") String verificationPassword)
{
......@@ -70,14 +78,31 @@ public class MigrationController extends BaseController
bindingResult.addError(verificationError);
}
model.addAttribute(ModelAttributes.MIGRATION_SETTINGS, migrationSettings);
if(bindingResult.hasErrors())
{
model.addAttribute(ModelAttributes.ERROR, bindingResult);
model.addAttribute(ModelAttributes.MIGRATION_SETTINGS, migrationSettings);
return ReturnValues.MIGRATION_SETTINGS;
}
// TODO
try
{
final MigrationArguments migrationArguments = new MigrationArguments.MigrationArgumentBuilder()
.withSourceUrl(migrationService.getDatabaseFromPreviousVersionPathWithoutExtension().toString())
.withDestinationUrl(migrationSettings.hostname(), migrationSettings.port(), migrationSettings.databaseName())
.withDestinationCredentials(migrationSettings.username(), migrationSettings.password())
.build();
// TODO: run non-blocking and redirect to progress page
migrationService.runMigration(migrationArguments);
}
catch(MigrationException e)
{
WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.migration.error", e.getMessage()), NotificationType.ERROR));
return ReturnValues.MIGRATION_SETTINGS;
}
// TODO: redirect to success page
return ReturnValues.MIGRATION_SETTINGS;
}
}
\ No newline at end of file
package de.deadlocker8.budgetmaster.migration;
public class MigrationException extends Exception
{
public MigrationException(String message)
{
super(message);
}
public MigrationException(String message, Throwable cause)
{
super(message, cause);
}
}
......@@ -7,16 +7,28 @@ import de.deadlocker8.budgetmaster.settings.SettingsService;
import de.deadlocker8.budgetmaster.templates.TemplateRepository;
import de.deadlocker8.budgetmaster.transactions.TransactionRepository;
import de.deadlocker8.budgetmaster.utils.Mappings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
public class MigrationService
{
private static final Logger LOGGER = LoggerFactory.getLogger(MigrationService.class);
public static final String PREVIOUS_DATABASE_FILE_NAME = "budgetmaster.mv.db";
public static final String PREVIOUS_DATABASE_FILE_NAME_WITHOUT_EXTENSION = "budgetmaster";
private final SettingsService settingsService;
private final Path applicationSupportFolder;
private final AccountRepository accountRepository;
......@@ -76,9 +88,79 @@ public class MigrationService
return templateRepository.findAll().isEmpty();
}
private Path getDatabaseFromPreviousVersionPath()
{
return applicationSupportFolder.resolve(PREVIOUS_DATABASE_FILE_NAME);
}
public Path getDatabaseFromPreviousVersionPathWithoutExtension()
{
return applicationSupportFolder.resolve(PREVIOUS_DATABASE_FILE_NAME_WITHOUT_EXTENSION);
}
private boolean isDatabaseFromPreviousVersionExisting()
{
final Path previousDatabasePath = applicationSupportFolder.resolve(PREVIOUS_DATABASE_FILE_NAME);
return Files.exists(previousDatabasePath);
return Files.exists(getDatabaseFromPreviousVersionPath());
}
public String runMigration(MigrationArguments migrationArguments) throws MigrationException
{
// TODO: extract BudgetMasterMigrator.jar from resources to tmp folder
return runMigrator(migrationArguments);
}
private String runMigrator(MigrationArguments migrationArguments) throws MigrationException
{
final String javaCommand = determineJavaCommand();
final List<String> command = new ArrayList<>();
command.add(MessageFormat.format("\"{0}\"", javaCommand));
command.add("-jar");
command.add("C:/Programmierung/BudgetMaster/BudgetMasterDatabaseMigrator/target/BudgetMasterDatabaseMigrator-v2.10.0.jar");
command.addAll(migrationArguments.getArguments());
LOGGER.debug("Starting migration with command: {}", command);
try
{
final ProcessBuilder processBuilder = new ProcessBuilder(command).redirectErrorStream(true);
final Process process = processBuilder.start();
final StringBuilder collectedStdout = new StringBuilder();
try(BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())))
{
while(true)
{
String line = in.readLine();
if(line == null)
{
break;
}
LOGGER.debug("[MIGRATOR] {}", line);
collectedStdout.append(line);
collectedStdout.append("\n");
}
}
LOGGER.debug("Migration process finished");
return collectedStdout.toString();
}
catch(IOException e)
{
throw new MigrationException("Error during migration process", e);
}
}
private String determineJavaCommand() throws MigrationException
{
final Optional<String> commandResultOptional = ProcessHandle.current().info().command();
if(commandResultOptional.isEmpty())
{
throw new MigrationException("Could not determine java executable");
}
return commandResultOptional.get().replace("\\", "/");
}
}
......@@ -172,6 +172,7 @@ notification.settings.update.available=BudgetMaster Update "{0}" verfügbar
notification.settings.database.delete.success=Datenbank erfolgreich gelöscht
notification.settings.backup.run.success=Backup erfolgreich
notification.settings.backup.run.error=Backup fehlgeschlagen
notification.migration.error=Migration fehlgeschlagen: {0}
upload.image.success=Erfolgreich hochgeladen
upload.image.error=Fehler: {0}
upload.image.error.no.file=Fehler: Keine Datei für Upload angegeben
......
......@@ -173,6 +173,7 @@ notification.settings.update.available=BudgetMaster update "{0}" available
notification.settings.database.delete.success=Successfully deleted database
notification.settings.backup.run.success=Backup successful
notification.settings.backup.run.error=Backup failed
notification.migration.error=Migration failed: {0}
upload.image.success=Upload successful
upload.image.error=Error: {0}
upload.image.error.no.file=Error: No file provided for upload
......
......@@ -17,6 +17,8 @@
<div class="headline">${locale.getString("title.migration")}</div>
</div>
</div>
<@header.content>
<div class="container">
<form name="MigrationSettings" action="<@s.url '/migration'/>" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
......@@ -103,6 +105,7 @@
</div>
</form>
</div>
</@header.content>
</div>
</main>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment