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

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.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.List;
import java.util.Locale;
import java.util.concurrent.TimeoutException;

public class HsqlDatabase extends FileBasedDatabase
{
	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", new RunnerDependency("org.hsqldb", "hsqldb", getBoxConfiguration().getDatabaseVersion()));

			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(new RunnerDependency("org.hsqldb", "hsqldb", getBoxConfiguration().getDatabaseVersion()), "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()));
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy