au.net.causal.maven.plugins.boxdb.db.HsqlDatabase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of boxdb-maven-plugin Show documentation
Show all versions of boxdb-maven-plugin Show documentation
Maven plugin to start databases using Docker and VMs
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 extends DatabaseLog> 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 extends RunnerDependency> 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 extends ImageComponent> checkImage()
throws BoxDatabaseException
{
return Collections.singleton(ImageCheckerUtils.checkImageUsingMavenDependencies("HSQL database", getContext(), getHsqlDatabaseDependencies()));
}
}