Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
au.net.causal.maven.plugins.boxdb.db.H2Database 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 com.google.common.collect.ImmutableList;
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.IOException;
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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeoutException;
public class H2Database extends FileBasedDatabase
{
static final String H2_DATABASE_GROUP_ID = "com.h2database";
static final String H2_DATABASE_ARTIFACT_ID = "h2";
private static volatile boolean h2Running;
public H2Database(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() + ".mv.db");
return Files.exists(databaseDir);
}
@Override
public boolean isRunning()
throws BoxDatabaseException
{
return h2Running;
}
@Override
public void start()
throws BoxDatabaseException
{
Path dbDir = getBoxConfiguration().getDatabaseFile().toAbsolutePath();
getContext().getLog().info("H2 home: " + dbDir);
try
{
JavaRunner runner = getContext().createJavaRunner("org.h2.tools.Server", getH2DatabaseDependencies());
List baseArgs = ImmutableList.of("-tcp", "-tcpPort", String.valueOf(getBoxConfiguration().getDatabasePort()),
"-baseDir", dbDir.toString());
List fullArgs = ImmutableList.builder().addAll(baseArgs)
.add("-ifNotExists")
.build();
//Multiple attempts to launch, try with -ifNotExists but this isn't needed in older versions
//so if the first attempt fails launch without that option
List argSets = ImmutableList.of(fullArgs.toArray(new String[0]),
baseArgs.toArray(new String[0]));
InvocationTargetException lastError = null;
Iterator i = argSets.iterator();
do
{
String[] args = i.next();
try
{
runner.execute(args);
lastError = null;
}
catch (InvocationTargetException e)
{
lastError = e;
if (!isUnsupportedServerOptionError(e))
throw e;
//If we know the exception was due to an unsupported arg, continue on
getContext().getLog().debug("Got an unsupported arg error so attempting with different args: " + e, e);
}
}
while (i.hasNext() && lastError != null);
if (lastError != null)
throw lastError;
h2Running = true;
}
catch (DependencyResolutionException e)
{
throw new BoxDatabaseException("Error resolving dependencies for H2: " + e, e);
}
catch (ClassNotFoundException | NoSuchMethodException | IOException | InvocationTargetException e)
{
throw new BoxDatabaseException("Error executing H2: " + e, e);
}
}
private static boolean isUnsupportedServerOptionError(InvocationTargetException e)
{
if (e.getCause() instanceof SQLException)
{
SQLException ex = (SQLException)e.getCause();
if (ex.getErrorCode() == 50100) //ErrorCode.FEATURE_NOT_SUPPORTED_1
return true;
}
return false;
}
@Override
public void stop()
throws BoxDatabaseException
{
try
{
JavaRunner runner = getContext().createJavaRunner("org.h2.tools.Server", getH2DatabaseDependencies());
runner.execute("-tcpShutdown", "tcp://localhost:" + getBoxConfiguration().getDatabasePort());
}
catch (InvocationTargetException e)
{
//H2 also registers shutdown hook, so if this executes as well we have two shutdown calls,
//the 2nd failing with SQL error "function STOP_SERVER not found" which we can safely ignore
if (e.getTargetException() instanceof SQLException)
{
SQLException sqlException = (SQLException)e.getTargetException();
if (sqlException.getErrorCode() == 90022) //Function not found
{
getContext().getLog().debug("Got a SQL error while shutting down H2 database, this is probably OK: " + e.getTargetException(), e);
return;
}
}
throw new BoxDatabaseException("Error executing H2: " + e, e);
}
catch (DependencyResolutionException e)
{
throw new BoxDatabaseException("Error resolving dependencies for H2: " + e, e);
}
catch (ClassNotFoundException | NoSuchMethodException | IOException e)
{
throw new BoxDatabaseException("Error executing H2: " + e, e);
}
}
@Override
public void createAndStart()
throws BoxDatabaseException
{
start();
}
@Override
public void delete()
throws BoxDatabaseException
{
getContext().getLog().info("Deleting H2 DB " + getBoxConfiguration().getDatabaseFile());
try
{
FileUtils.deleteDirectory(getBoxConfiguration().getDatabaseFile().toFile());
}
catch (IOException e)
{
throw new BoxDatabaseException("Failed to delete H2 database in " + getBoxConfiguration().getDatabaseFile() + ": " + e, e);
}
}
@Override
public void deleteImage()
throws BoxDatabaseException
{
//Not going to wipe H2 from local repo, so do nothing
}
@Override
public JdbcConnectionInfo jdbcConnectionInfo(DatabaseTarget target)
throws BoxDatabaseException
{
String uri = "jdbc:h2:tcp://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(getH2DatabaseDependencies(), "org.h2.Driver");
}
@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.h2.jdbcx.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 IF NOT EXISTS " + getBoxConfiguration().getDatabaseUser().toUpperCase(Locale.ENGLISH) + " PASSWORD " + sqlString(getBoxConfiguration().getDatabasePassword()) + " ADMIN");
if (!getBoxConfiguration().getDatabaseUser().equals(getBoxConfiguration().getAdminUser()))
stat.execute("CREATE USER IF NOT EXISTS " + getBoxConfiguration().getAdminUser().toUpperCase(Locale.ENGLISH) + " PASSWORD " + sqlString(getBoxConfiguration().getAdminPassword()) + " ADMIN");
if (!StringUtils.isEmpty(getBoxConfiguration().getDatabaseCollation()))
{
String[] splitCollation = getBoxConfiguration().getDatabaseCollation().split(";", 2);
String locale = splitCollation[0];
String collationStatement = "SET DATABASE COLLATION " + locale;
if (splitCollation.length > 1)
{
String collationStrength = splitCollation[1];
collationStatement = collationStatement + " STRENGTH " + collationStrength;
}
stat.execute(collationStatement);
}
else
stat.execute("SET DATABASE COLLATION OFF");
}
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 H2 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 TO '" + 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);
return;
}
//Otherwise it was a script backup, so use the database to restore
executeScript(backupResource, DatabaseTarget.ADMIN, getProjectConfiguration().getBackupTimeout());
}
@Override
public List extends DatabaseLog> logFiles() throws BoxDatabaseException, IOException
{
//There might be a trace log file if this was configured when the user opened the DB
//So we'll use that as the main log file
Path traceLogFile = getBoxConfiguration().getDatabaseFile().resolve(getBoxConfiguration().getDatabaseName() + ".trace.db");
//Assume log file is written in platform default encoding
return Collections.singletonList(new FileDatabaseLog(traceLogFile.getFileName().toString(), traceLogFile, Charset.defaultCharset()));
}
/**
* @return H2 database / driver dependencies.
*/
protected List extends RunnerDependency> getH2DatabaseDependencies()
{
return Collections.singletonList(new RunnerDependency(H2_DATABASE_GROUP_ID, H2_DATABASE_ARTIFACT_ID,
getBoxConfiguration().getDatabaseVersion()));
}
@Override
public void prepareImage()
throws BoxDatabaseException
{
try
{
DependencyUtils.resolveDependencies(getH2DatabaseDependencies(),
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("H2 database", getContext(), getH2DatabaseDependencies()));
}
}