All Downloads are FREE. Search and download functionalities are using the official Maven repository.

au.net.causal.maven.plugins.boxdb.db.FileBasedDatabase Maven / Gradle / Ivy

There is a newer version: 3.3
Show newest version
package au.net.causal.maven.plugins.boxdb.db;

import au.net.causal.maven.plugins.boxdb.JdbcSqlRunner;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.codehaus.plexus.archiver.Archiver;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.codehaus.plexus.archiver.tar.TarUnArchiver;
import org.codehaus.plexus.archiver.util.DefaultFileSet;
import org.codehaus.plexus.util.FileUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeoutException;

/**
 * Superclass for databases that exist on the local filesystem.
 * 

* * Backups are implemented by tarring the directory containing the database files while the database is stopped. */ public abstract class FileBasedDatabase implements BoxDatabase { private final BoxConfiguration boxConfiguration; private final ProjectConfiguration projectConfiguration; private final BoxContext context; protected FileBasedDatabase(BoxConfiguration boxConfiguration, ProjectConfiguration projectConfiguration, BoxContext context) { Objects.requireNonNull(boxConfiguration, "boxConfiguration == null"); Objects.requireNonNull(projectConfiguration, "projectConfiguration == null"); Objects.requireNonNull(context, "context == null"); this.boxConfiguration = boxConfiguration; this.projectConfiguration = projectConfiguration; this.context = context; } @Override public void backup(Path backupFile, BackupFileTypeHint backupFileTypeHint) throws BoxDatabaseException, IOException, SQLException { //TODO would be nice to hint to caller whether backup is offline or online //File-based database backups must be done offline boolean runningBeforeBackup = isRunning(); if (runningBeforeBackup) { getContext().getLog().info("Stopping database to make offline backup."); stop(); } else { getContext().getLog().info("Database is already stopped before backup."); } Path dbDirectory = getBoxConfiguration().getDatabaseFile(); if (Files.notExists(dbDirectory)) { throw new SQLException("Cannot back up a database that has not been created yet.", new FileNotFoundException(dbDirectory.toAbsolutePath().toString())); } //Archive this directory createBackupArchiveFromDirectory(dbDirectory, backupFile); //Restore running state after offline backup if (runningBeforeBackup) startAndWait(); } protected void createBackupArchiveFromDirectory(Path directory, Path archiveFile) throws IOException, BoxDatabaseException { try { Archiver archiver = getContext().getArchiverManager().getArchiver("tar"); //TODO should we allow the archiver to be configured by configuration? archiver.setDestFile(archiveFile.toFile()); archiver.addFileSet(new DefaultFileSet(directory.toFile())); archiver.createArchive(); } catch (NoSuchArchiverException e) { throw new BoxDatabaseException("Could not find archiver: " + e, e); } } @Override public void restore(Path backupFile) throws BoxDatabaseException, IOException, SQLException { restore(backupFile.toUri().toURL()); /* This can be used if we don't need to support URLs not from the filesystem //Stop database if running boolean runningBeforeRestore = isRunning(); if (runningBeforeRestore) stop(); //Clean out existing database before restoring cleanDatabaseDirectory(); try { UnArchiver unArchiver = getContext().getArchiverManager().getUnArchiver("tar"); unArchiver.setDestDirectory(getBoxConfiguration().getDatabaseFile().toFile()); unArchiver.setSourceFile(backupFile.toFile()); unArchiver.extract(); } catch (NoSuchArchiverException e) { throw new BoxDatabaseException("Could not find unarchiver: " + e, e); } if (runningBeforeRestore) startAndWait(); */ } @Override public void restore(URL backupResource) throws BoxDatabaseException, IOException, SQLException { //Stop database if running boolean runningBeforeRestore = isRunning(); if (runningBeforeRestore) stop(); //Clean out existing database before restoring cleanDatabaseDirectory(); File destDirectory = getBoxConfiguration().getDatabaseFile().toFile(); //Extract from URL resource using a bit of a hack //The unarchiver has protected access to extractFile which does some fancy things regarding symlinks and permissions //that I don't want to just copy+paste MyTarUnArchiver unarchiver = new MyTarUnArchiver(); try (TarArchiveInputStream tis = new TarArchiveInputStream(backupResource.openStream())) { TarArchiveEntry te; while ( ( te = tis.getNextTarEntry() ) != null ) { final String symlinkDestination = te.isSymbolicLink() ? te.getLinkName() : null; unarchiver.extractFile( null, destDirectory, tis, te.getName(), te.getModTime(), te.isDirectory(), te.getMode() != 0 ? te.getMode() : null, symlinkDestination ); } } if (runningBeforeRestore) startAndWait(); } protected void startAndWait() throws BoxDatabaseException { start(); try { this.waitUntilStarted(projectConfiguration.getBackupTimeout()); } catch (TimeoutException e) { throw new BoxDatabaseException("Timeout waiting for database to start back up.", e); } } /** * Delete and recreate the database directory to prepare for restore. * * @throws IOException if an error occurs. */ protected void cleanDatabaseDirectory() throws IOException { Path databaseDirectory = getBoxConfiguration().getDatabaseFile(); if (Files.exists(databaseDirectory)) FileUtils.deleteDirectory(databaseDirectory.toFile()); Files.createDirectories(databaseDirectory); } /** * Detect whether the specified backup resource is a TAR archive created with this class's backup. * Return value of false means it's not a TAR file. Use this method if subclass has overridden backup * to support backup hints that conditionally delegates to this class's method depending on * backup hint. * * @param backupResource the resource file. * * @return true if the file was an archive likely created with this class's backup method, false if not. * * @throws IOException if an I/O error occurs. */ protected boolean isBackupArchive(URL backupResource) throws IOException { try (TarArchiveInputStream tis = new TarArchiveInputStream(backupResource.openStream())) { tis.getNextTarEntry(); return true; } catch (IOException e) { getContext().getLog().debug("Backup resource " + backupResource.toExternalForm() + " detected as not a TAR.", e); //No specific invalid format exception from TarInputStream, so assume all I/O exceptions //mean the format is not a TAR return false; } } @Override public void executeJdbcScript(Reader scriptReader, DatabaseTarget targetDatabase) throws IOException, SQLException, BoxDatabaseException { try (Connection con = createJdbcConnection(targetDatabase); JdbcSqlRunner sqlRunner = new JdbcSqlRunner(con, getContext().getLog())) { sqlRunner.executeSql(new BufferedReader(scriptReader)); } } protected BoxConfiguration getBoxConfiguration() { return boxConfiguration; } protected ProjectConfiguration getProjectConfiguration() { return projectConfiguration; } protected BoxContext getContext() { return context; } /** * This is a hack class to re-use logic from Tar Unarchiver to extract files. extractFile() is protected in * TarArchiver class so we make a subclass and override this method to make it public and usable. */ private static class MyTarUnArchiver extends TarUnArchiver { @Override public void extractFile(File srcF, File dir, InputStream compressedInputStream, String entryName, Date entryDate, boolean isDirectory, Integer mode, String symlinkDestination) throws IOException, ArchiverException { super.extractFile(srcF, dir, compressedInputStream, entryName, entryDate, isDirectory, mode, symlinkDestination); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy