Commit 50d7e64f authored by Luna Riegel's avatar Luna Riegel
Browse files

Feature: Add configuration of embedded database

parent 61859d4f
package de.hft.stuttgart.citydoctor2.database;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import java.util.Properties;
public final class DataBaseSettings {
private static final Logger logger = LogManager.getLogger(DataBaseSettings.class);
private static final Properties props;
private DataBaseSettings() {}
static {
props = new Properties();
File propFile = new File("DBSettings.properties");
if (propFile.exists()) {
try (BufferedReader bis = new BufferedReader(new FileReader(propFile))) {
props.load(bis);
} catch (IOException e) {
logger.error("Failed to load database settings", e);
}
}
// props.setProperty("name","cd_db");
// props.setProperty("directory","/database/");
// props.setProperty("poolSize","10");
// props.setProperty("inMemoryMode","false");
// props.setProperty("attemptFallback","true");
// props.setProperty("debugMode","false");
}
public static EmbeddedDatabaseConfiguration getConfig() {
EmbeddedDatabaseConfiguration defaultConfig = new EmbeddedDatabaseConfiguration();
if (props.isEmpty()) {
logger.debug("No database settings found, using default configuration");
return defaultConfig;
}
String name = props.getProperty("name");
String directory = props.getProperty("directory");
String poolSizeString = props.getProperty("poolSize");
String inMemoryModeString = props.getProperty("inMemoryMode");
String attemptFallbackString = props.getProperty("attemptFallback");
String debugModeString = props.getProperty("debugMode");
boolean inMemoryMode = Boolean.parseBoolean(inMemoryModeString);
boolean attemptFallback = attemptFallbackString == null || Boolean.parseBoolean(attemptFallbackString);
boolean debugMode = Boolean.parseBoolean(debugModeString);
int poolSize;
try {
poolSize = Integer.parseInt(poolSizeString);
if (poolSize < 1) {
//TODO: Localize String
logger.warn("poolSize property cannot be less than 1, using default value");
poolSize = defaultConfig.connectionPoolSize();
}
} catch (NumberFormatException e) {
//TODO: Localize String
logger.warn("poolSize property must be an integer, using default value");
poolSize = defaultConfig.connectionPoolSize();
}
if (name == null || name.isBlank()){
name = defaultConfig.databaseName();
}
if (directory == null || directory.isBlank()){
directory = defaultConfig.databaseDirectory();
}
try {
Paths.get(directory, name);
} catch (InvalidPathException e) {
if (!inMemoryMode) {
// Only warn if in-memory mode is not being used
//TODO: Localize String
logger.warn("Properties for directory and name do not form a valid path, using default values");
name = defaultConfig.databaseName();
directory = defaultConfig.databaseDirectory();
}
}
return new EmbeddedDatabaseConfiguration(name, directory, poolSize, inMemoryMode, attemptFallback, debugMode);
}
}
package de.hft.stuttgart.citydoctor2.database;
import java.io.File;
/**
* Record containing the configuration parameters for the setup of the embedded database
* @param databaseName Name of the database
* @param databaseDirectory Directory of the database. Relative paths will be resolved from the current working directory.
* CityDoctor will create the directory if it does not exist, and will overwrite an existing
* database-file if its name matches databaseName
* @param connectionPoolSize Size of the connection pool
* @param inMemoryMode If true, CityDoctor will create the embedded database in RAM
* @param attemptFallback If true, CityDoctor will attempt to create the embedded database in RAM if creation of the
* database-file fails. Will be ignored if inMemoryMode is true
* @param debugMode If true, CityDoctor will start the embedded database in automatic mixed mode to allow access to the
* database during runtime.
*/
public record EmbeddedDatabaseConfiguration(String databaseName, String databaseDirectory, int connectionPoolSize,
boolean inMemoryMode, boolean attemptFallback, boolean debugMode) {
private static final String IN_MEMORY_PREFIX= "jdbc:h2:mem";
private static final String AUTO_SERVER_PARAMETER = ";AUTO_SERVER=TRUE";
/**
* Instantiates the default configuration for the embedded database.
*/
EmbeddedDatabaseConfiguration() {
this("cd_db", File.separator + "database"+ File.separator, Runtime.getRuntime().availableProcessors(),
false, true, false);
}
public String getInMemoryH2Url() {
return IN_MEMORY_PREFIX + databaseName;
}
public String getH2FileUrl(){
return databaseDirectory + databaseName;
}
public String getH2ParametersSuffix(){
if (debugMode){
return AUTO_SERVER_PARAMETER;
} else {
return ";";
}
}
}
...@@ -8,6 +8,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.CityObject; ...@@ -8,6 +8,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId; import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.exceptions.EmbeddedDatabaseHandlerException;
import de.hft.stuttgart.citydoctor2.utils.Localization; import de.hft.stuttgart.citydoctor2.utils.Localization;
import org.apache.commons.lang3.SerializationException; import org.apache.commons.lang3.SerializationException;
import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.SerializationUtils;
...@@ -16,6 +17,7 @@ import org.apache.logging.log4j.Logger; ...@@ -16,6 +17,7 @@ import org.apache.logging.log4j.Logger;
import org.h2gis.functions.factory.H2GISDBFactory; import org.h2gis.functions.factory.H2GISDBFactory;
import org.h2gis.utilities.wrapper.DataSourceWrapper; import org.h2gis.utilities.wrapper.DataSourceWrapper;
import javax.sql.DataSource;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
...@@ -34,34 +36,54 @@ import java.util.Map; ...@@ -34,34 +36,54 @@ import java.util.Map;
public class EmbeddedDatabaseHandler { public class EmbeddedDatabaseHandler {
private static final Logger logger = LogManager.getLogger(EmbeddedDatabaseHandler.class); private static final Logger logger = LogManager.getLogger(EmbeddedDatabaseHandler.class);
private static final String DB_NAME = "/database/cd_db"; private final EmbeddedDatabaseConfiguration config;
private DataSourceWrapper dataSource; private DataSourceWrapper dataSource;
public EmbeddedDatabaseHandler(){ public EmbeddedDatabaseHandler(EmbeddedDatabaseConfiguration dbConfig){
config = dbConfig;
String jdbcUrl;
try { try {
String jdbcurl = H2GISDBFactory.createDataSource(DB_NAME, jdbcUrl = createEmbeddedDataBase(config.getH2FileUrl());
true, ";AUTO_SERVER=TRUE").getConnection().getMetaData().getURL(); logger.debug("1] {}",jdbcUrl);
logger.debug("1] {}",jdbcurl);
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(jdbcurl);
ds.setUsername("sa");
ds.setPassword("sa");
int coreCount = Runtime.getRuntime().availableProcessors();
ds.setMaximumPoolSize(coreCount*5);
ds.setMinimumIdle(coreCount*5);
dataSource = new DataSourceWrapper(ds);
setupFeaturesTable();
//Cleanup hook
Runtime.getRuntime().addShutdownHook(new Thread(ds::close));
} catch (Exception e) { } catch (Exception e) {
logger.fatal(Localization.getText("DatabaseHandler.setupFailure")); logger.fatal(Localization.getText("DatabaseHandler.setupFailure"));
logger.fatal(e.getMessage()); logger.fatal(e.getMessage());
if (config.attemptFallback()){
logger.info("Attempting fallback to in-memory database");
try {
jdbcUrl = createEmbeddedDataBase(config.getInMemoryH2Url());
} catch (Exception e1) {
throw new EmbeddedDatabaseHandlerException("Fallback to in-memory database failed, embedded database could not be created", e1);
}
logger.info("Fallback to in-memory database succeeded");
logger.warn("The in-memory database is limited by the system's available RAM. Loading big CityGML files can lead to OutOfMemory errors.");
} else {
throw new EmbeddedDatabaseHandlerException("Embedded database could not be created");
}
} }
setupHikariPool(jdbcUrl);
setupFeaturesTable();
}
private void setupHikariPool(String jdbcUrl){
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(jdbcUrl);
ds.setUsername("sa");
ds.setPassword("sa");
ds.setMaximumPoolSize(config.connectionPoolSize());
dataSource = new DataSourceWrapper(ds);
//Cleanup hook
Runtime.getRuntime().addShutdownHook(new Thread(ds::close));
}
private String createEmbeddedDataBase(String dbUrl) throws SQLException {
DataSource source = H2GISDBFactory.createDataSource(dbUrl,
true, config.getH2ParametersSuffix());
try (Connection con = source.getConnection()) {
return con.getMetaData().getURL();
}
} }
...@@ -85,6 +107,7 @@ public class EmbeddedDatabaseHandler { ...@@ -85,6 +107,7 @@ public class EmbeddedDatabaseHandler {
} catch (SQLException e) { } catch (SQLException e) {
logger.fatal(Localization.getText("DatabaseHandler.tableFailure")); logger.fatal(Localization.getText("DatabaseHandler.tableFailure"));
logger.fatal(e.getMessage()); logger.fatal(e.getMessage());
throw new EmbeddedDatabaseHandlerException("Could not setup features table in database", e);
} }
} }
......
...@@ -42,11 +42,15 @@ public class FeatureCache implements CityObjectCache{ ...@@ -42,11 +42,15 @@ public class FeatureCache implements CityObjectCache{
private final ConcurrentHashMap<Thread, GmlId> evictionLocks = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Thread, GmlId> evictionLocks = new ConcurrentHashMap<>();
public FeatureCache() { public FeatureCache() {
handler = new EmbeddedDatabaseHandler(); this(DataBaseSettings.getConfig());
}
public FeatureCache(EmbeddedDatabaseConfiguration dbConfig){
handler = new EmbeddedDatabaseHandler(dbConfig);
cache = Caffeine.newBuilder().maximumSize(3000).removalListener((GmlId key, CityObject value, RemovalCause cause) -> { cache = Caffeine.newBuilder().maximumSize(3000).removalListener((GmlId key, CityObject value, RemovalCause cause) -> {
if (value != null && cause.wasEvicted() && isFeatureMarshallable(value)){ if (value != null && cause.wasEvicted() && isFeatureMarshallable(value)){
handler.marshallCityObject(value); handler.marshallCityObject(value);
} }
}).build(handler::unmarshallCityObject); }).build(handler::unmarshallCityObject);
} }
......
package de.hft.stuttgart.citydoctor2.exceptions;
import java.io.Serial;
public class EmbeddedDatabaseHandlerException extends RuntimeException{
@Serial
private static final long serialVersionUID = -7848325704234347425L;
public EmbeddedDatabaseHandlerException(String message) {
super(message);
}
public EmbeddedDatabaseHandlerException(String message, Throwable cause) {
super(message, cause);
}
}
...@@ -166,7 +166,7 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -166,7 +166,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private static final Logger logger = LogManager.getLogger(Citygml3FeatureMapper.class); private static final Logger logger = LogManager.getLogger(Citygml3FeatureMapper.class);
private static FeatureCache featureCache = new FeatureCache(); private static final FeatureCache featureCache = new FeatureCache();
private final CityDoctorModel model; private final CityDoctorModel model;
private final ParserConfiguration config; private final ParserConfiguration config;
private final Path directory; private final Path directory;
......
...@@ -29,12 +29,10 @@ import static org.junit.Assert.fail; ...@@ -29,12 +29,10 @@ import static org.junit.Assert.fail;
public class EmbeddedDataBaseHandlerTest { public class EmbeddedDataBaseHandlerTest {
private EmbeddedDatabaseHandler handler; private EmbeddedDatabaseHandler handler = new EmbeddedDatabaseHandler(new EmbeddedDatabaseConfiguration());
@Before @Before
public void resetDatabase() { public void resetDatabase() {
try { try {
handler = new EmbeddedDatabaseHandler();
handler.setupFeaturesTable(); handler.setupFeaturesTable();
} catch (Exception e) { } catch (Exception e) {
fail(e.getMessage()); fail(e.getMessage());
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment