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

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

package au.net.causal.maven.plugins.boxdb.db;

import au.net.causal.maven.plugins.boxdb.DependencyUtils;
import au.net.causal.maven.plugins.boxdb.ImageCheckerUtils;
import au.net.causal.maven.plugins.boxdb.JavaRunner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.resolution.DependencyResolutionException;

import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeoutException;

public class HsqlDatabase extends FileBasedDatabase
{
    static final String HSQL_DATABASE_GROUP_ID = "org.hsqldb";
    static final String HSQL_DATABASE_ARTIFACT_ID = "hsqldb";

    private static volatile boolean hsqlRunning;

    public HsqlDatabase(BoxConfiguration boxConfiguration,
                        ProjectConfiguration projectConfiguration,
                        BoxContext context)
    {
        super(boxConfiguration, projectConfiguration, context);
    }

    @Override
    public boolean exists()
    throws BoxDatabaseException
    {
        if (!Files.exists(getBoxConfiguration().getDatabaseFile()))
            return false;

        //Also check databaseName exists underneath
        Path databaseDir = getBoxConfiguration().getDatabaseFile().resolve(getBoxConfiguration().getDatabaseName() + ".script");
        return Files.exists(databaseDir);
    }

    @Override
    public boolean isRunning()
    throws BoxDatabaseException
    {
        return hsqlRunning;
    }

    @Override
    public void start()
    throws BoxDatabaseException
    {
        Path dbDir = getBoxConfiguration().getDatabaseFile().resolve(getBoxConfiguration().getDatabaseName()).toAbsolutePath();
        getContext().getLog().info("HSQLDB home: " + dbDir);

        try
        {
            JavaRunner runner = getContext().createJavaRunner("org.hsqldb.Server", getHsqlDatabaseDependencies());

            runner.execute("--port", String.valueOf(getBoxConfiguration().getDatabasePort()),
                    "--database.0", "file:" + dbDir.toString().replace(File.separatorChar, '/'),
                    "--dbname.0", getBoxConfiguration().getDatabaseName(),
                    "--no_system_exit", "true");

            hsqlRunning = true;
        }
        catch (DependencyResolutionException e)
        {
            throw new BoxDatabaseException("Error resolving dependencies for HSQLDB: " + e, e);
        }
        catch (ClassNotFoundException | NoSuchMethodException | IOException | InvocationTargetException e)
        {
            throw new BoxDatabaseException("Error executing HSQLDB: " + e, e);
        }
    }

    @Override
    public void stop()
    throws BoxDatabaseException
    {
        try
        {
            executeSql("SHUTDOWN", DatabaseTarget.ADMIN, getProjectConfiguration().getScriptTimeout());
        }
        catch (IOException | SQLException e)
        {
            throw new BoxDatabaseException("Error shutting down database: " + e, e);
        }
    }

    @Override
    public void createAndStart()
    throws BoxDatabaseException
    {
        start();
    }

    @Override
    public void delete()
    throws BoxDatabaseException
    {
        getContext().getLog().info("Deleting HSQLDB DB " + getBoxConfiguration().getDatabaseFile());

        try
        {
            FileUtils.deleteDirectory(getBoxConfiguration().getDatabaseFile().toFile());
        }
        catch (IOException e)
        {
            throw new BoxDatabaseException("Failed to delete HSQLDB database in " + getBoxConfiguration().getDatabaseFile() + ": " + e, e);
        }
    }

    @Override
    public void deleteImage()
    throws BoxDatabaseException
    {
        //Not going to wipe HSQL from local repo, so do nothing
    }

    @Override
    public JdbcConnectionInfo jdbcConnectionInfo(DatabaseTarget target)
    throws BoxDatabaseException
    {
        String uri = "jdbc:hsqldb:hsql://localhost:" + getBoxConfiguration().getDatabasePort() + "/" + getBoxConfiguration().getDatabaseName();
        return new JdbcConnectionInfo(uri,
                    target.user(getBoxConfiguration()), target.password(getBoxConfiguration()),
                    "localhost", getBoxConfiguration().getDatabasePort());
    }

    @Override
    public JdbcDriverInfo jdbcDriverInfo()
    throws BoxDatabaseException
    {
        return new JdbcDriverInfo(getHsqlDatabaseDependencies(), "org.hsqldb.jdbcDriver");
    }

    @Override
    public void waitUntilStarted(Duration maxTimeToWait)
    throws TimeoutException, BoxDatabaseException
    {
        DatabaseUtils.waitUntilTcpPortResponding(maxTimeToWait, this, getContext());
    }

    @Override
    public void executeScript(URL script, DatabaseTarget targetDatabase, Duration timeout)
    throws IOException, SQLException, BoxDatabaseException
    {
        try (Reader reader = new InputStreamReader(script.openStream(), StandardCharsets.UTF_8))
        {
            executeScript(reader, targetDatabase, timeout);
        }
    }

    protected DataSourceBuilder dataSourceBuilder(DatabaseTarget target)
    throws BoxDatabaseException
    {
        DataSourceBuilder builder = new DataSourceBuilder(getContext())
                .dataSourceClassName("org.hsqldb.jdbc.JDBCDataSource")
                .dependencies(jdbcDriverInfo().getDependencies())
                .configureDataSource("setUrl", String.class, jdbcConnectionInfo(target).getUri());

        if (target == DatabaseTarget.USER)
        {
            builder.configureDataSource("setUser", String.class, target.user(getBoxConfiguration()));
            builder.configureDataSource("setPassword", String.class, target.password(getBoxConfiguration()));
        }

        return builder;
    }
    
    @Override
    public Connection createJdbcConnection(DatabaseTarget targetDatabase)
    throws SQLException, BoxDatabaseException, IOException
    {
        DataSource dataSource = dataSourceBuilder(targetDatabase).create();
        return dataSource.getConnection();
    }

    @Override
    public void executeScript(Reader scriptReader, DatabaseTarget targetDatabase, Duration timeout)
    throws IOException, SQLException, BoxDatabaseException
    {
        executeJdbcScript(scriptReader, targetDatabase);
    }

    @Override
    public void executeSql(String sql, DatabaseTarget targetDatabase, Duration timeout)
    throws IOException, SQLException, BoxDatabaseException
    {
        try (Reader reader = new StringReader(sql))
        {
            executeScript(reader, targetDatabase, timeout);
        }
    }

    @Override
    public String getName()
    {
        return getBoxConfiguration().getDatabaseName();
    }

    @Override
    public void configureNewDatabase()
    throws IOException, SQLException, BoxDatabaseException
    {
        DataSource dataSource = dataSourceBuilder(DatabaseTarget.ADMIN).create();
        try (Connection con = dataSource.getConnection())
        {
            //This creates the user, now run a command to set up the database user
            try (Statement stat = con.createStatement())
            {
                stat.execute("CREATE USER \"" + escapeSqlString(getBoxConfiguration().getDatabaseUser()) + "\" PASSWORD " + sqlString(getBoxConfiguration().getDatabasePassword()) + " ADMIN");

                if (!StringUtils.isEmpty(getBoxConfiguration().getDatabaseCollation()))
                {
                    String collationStatement = "SET DATABASE COLLATION \"" + getBoxConfiguration().getDatabaseCollation() + "\"";
                    stat.execute(collationStatement);
                }
            }

            if (!StringUtils.isEmpty(getBoxConfiguration().getDatabaseEncoding()) &&
                !getBoxConfiguration().getDatabaseEncoding().equalsIgnoreCase(CommonEncoding.UNICODE.name()))
            {
                throw new BoxDatabaseException("Unsupported database encoding '" + getBoxConfiguration().getDatabaseEncoding() +
                        "', only '" + CommonEncoding.UNICODE.name().toLowerCase(Locale.ENGLISH) + "' is supported.");
            }

            getContext().getLog().info("Created HSQL database " + getName());
        }
    }

    private String sqlString(String s)
    {
        StringBuilder buf = new StringBuilder();
        buf.append('\'');
        for (char c : s.toCharArray())
        {
            if (c == '\'')
                buf.append("\'\'");
            else
                buf.append(c);
        }
        buf.append('\'');

        return buf.toString();
    }

    @Override
    public void backup(Path backupFile, BackupFileTypeHint backupFileTypeHint)
    throws BoxDatabaseException, IOException, SQLException
    {
        //Binary backup type can be handled by superclass
        if (backupFileTypeHint == BackupFileTypeHint.COMPACT)
            super.backup(backupFile, backupFileTypeHint);
        else
            executeSql("SCRIPT '" + escapeSqlString(backupFile.toAbsolutePath().toString()) + "'", DatabaseTarget.USER, getProjectConfiguration().getBackupTimeout());
    }

    private String escapeSqlString(String s)
    {
        s = s.replace("'", "''");
        return s;
    }

    @Override
    public void restore(URL backupResource)
    throws BoxDatabaseException, IOException, SQLException
    {
        //If it's a full archive backup, then just use superclass
        if (isBackupArchive(backupResource))
            super.restore(backupResource);
        else
            restoreFromFullScriptFile(backupResource);
    }

    private void restoreFromFullScriptFile(URL resource)
    throws BoxDatabaseException, IOException
    {
        //Stop database if running
        boolean runningBeforeRestore = isRunning();
        if (runningBeforeRestore)
            stop();

        //Clean out existing database before restoring
        cleanDatabaseDirectory();

        //Restore the script file
        Path targetScriptFile = getBoxConfiguration().getDatabaseFile().resolve(getBoxConfiguration().getDatabaseName() + ".script");
        try (InputStream is = resource.openStream())
        {
            Files.copy(is, targetScriptFile);
        }

        if (runningBeforeRestore)
            startAndWait();
    }

    @Override
    public List logFiles() throws BoxDatabaseException, IOException
    {
        //This event log may be set up by special SQL commands or properties, so we'll pull it out if it exists
        Path traceLogFile = getBoxConfiguration().getDatabaseFile().resolve(getBoxConfiguration().getDatabaseName() + ".app.log");
        Path sqlTraceLogFile = getBoxConfiguration().getDatabaseFile().resolve(getBoxConfiguration().getDatabaseName() + ".sql.log");
        
        //Assume log file is written in platform default encoding
        return Arrays.asList(new FileDatabaseLog(traceLogFile.getFileName().toString(), traceLogFile, Charset.defaultCharset()),
                            new FileDatabaseLog(sqlTraceLogFile.getFileName().toString(), sqlTraceLogFile, Charset.defaultCharset()));
    }

    /**
     * @return HSQL server / driver dependencies.
     */
    protected List getHsqlDatabaseDependencies()
    {
        return Collections.singletonList(new RunnerDependency(HSQL_DATABASE_GROUP_ID, HSQL_DATABASE_ARTIFACT_ID,
                                                              getBoxConfiguration().getDatabaseVersion()));
    }

    @Override
    public void prepareImage()
    throws BoxDatabaseException
    {
        try
        {
            DependencyUtils.resolveDependencies(getHsqlDatabaseDependencies(),
                                                getContext().getRepositorySystem(),
                                                getContext().getRepositorySystemSession(),
                                                getContext().getRemoteRepositories());
        }
        catch (DependencyResolutionException e)
        {
            throw new BoxDatabaseException("Failed to download dependencies for database: " + e.getMessage(), e);
        }
    }

    @Override
    public Collection checkImage()
    throws BoxDatabaseException
    {
        return Collections.singleton(ImageCheckerUtils.checkImageUsingMavenDependencies("HSQL database", getContext(), getHsqlDatabaseDependencies()));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy