
com.d3x.core.db.DatabaseConfig Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2018-2019 D3X Systems - All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.d3x.core.db;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import com.d3x.core.json.Json;
import com.d3x.core.json.JsonEngine;
import com.d3x.core.json.JsonSchema;
import com.d3x.core.util.Crypto;
import com.d3x.core.util.IO;
import com.d3x.core.util.Option;
import com.d3x.core.util.Secret;
import com.google.gson.Gson;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
/**
* A class that capture database configuration details
*
* @author Xavier Witdouck
*/
@lombok.ToString()
@lombok.EqualsAndHashCode()
@lombok.AllArgsConstructor()
@lombok.Builder(toBuilder=true)
@lombok.extern.slf4j.Slf4j()
public class DatabaseConfig {
public static final JsonSchema schema = JsonSchema.of(DatabaseConfig.class, "db-config", 1);
/** The driver definition */
@lombok.Getter @lombok.NonNull
private DatabaseDriver driver;
/** The JDBC connection url */
@lombok.Getter @lombok.NonNull
private String url;
/** The JDBC user name*/
@lombok.Getter @lombok.NonNull
private Option user;
/** The JDBC password */
@lombok.Getter @lombok.NonNull
private Option password;
/** The initial size of the connection pool */
@lombok.Getter @lombok.NonNull
private Option initialPoolSize;
/** The max size of connection pool */
@lombok.Getter @lombok.NonNull
private Option maxPoolSize;
/** The max number of idle connections in pool */
@lombok.Getter @lombok.NonNull
private Option maxPoolIdleSize;
/** True if connections should be set to read only */
@lombok.Getter @lombok.NonNull
private Option readOnly;
/** True to mark connection as auto commit */
@lombok.Getter @lombok.NonNull
private Option autoCommit;
/** The query time out in seconds */
@lombok.Getter @lombok.NonNull
private Option queryTimeOutSeconds;
/** The max time to wait for an available connection */
@lombok.Getter @lombok.NonNull
private Option maxWaitTimeMillis;
/** The default fetch size for statements */
@lombok.Getter @lombok.NonNull
private Option fetchSize;
/** The connection properties */
@lombok.NonNull
private Map properties;
/**
* Returns the map of connection properties
* @return the map of connection properties
*/
public Map getProperties() {
return Collections.unmodifiableMap(properties);
}
/**
* Returns a new database config for an H2 database
* @param dbFile the database file
* @param user the username
* @param password the password
* @return the config
*/
public static DatabaseConfig h2(File dbFile, String user, String password) {
return DatabaseConfig.of(config -> {
config.driver(DatabaseDriver.H2);
config.url("jdbc:h2:file:" + dbFile.getAbsolutePath());
config.user(Option.of(user));
config.password(Option.of(Secret.of(password)));
config.properties(new HashMap<>());
});
}
/**
* Returns a new config for MySQL with the details provided
* @param url the jdbc url
* @param username the jdbc username
* @param password the jdbc password
* @return the newly created config
*/
public static DatabaseConfig mysql(String url, String username, Secret password) {
return DatabaseConfig.of(config -> {
config.driver(DatabaseDriver.MYSQL);
config.url(url);
config.user(Option.of(username));
config.password(Option.of(password));
config.properties(new HashMap<>());
});
}
/**
* Returns a new database config for the args provided
* @param driver the database driver
* @param url the database url
* @param user the username
* @param password the password
* @return the config
*/
public static DatabaseConfig of(DatabaseDriver driver, String url, String user, Secret password) {
return DatabaseConfig.of(config -> {
config.driver(driver);
config.url(url);
config.user(Option.of(user));
config.password(Option.of(password));
config.properties(new HashMap<>());
});
}
/**
* Returns database config loaded from a JSON resource
* @param url the URL to load config from
* @param crypto the optional crypto if password is encrypted
* @return the database config
*/
public static DatabaseConfig fromJson(URL url, Option crypto) throws IOException {
return gson(crypto).fromJson(new InputStreamReader(url.openStream()), DatabaseConfig.class);
}
/**
* Returns the config loaded from a json file on classpath
* @param resource the path to json resource
* @return the database config
* @throws DatabaseException if fails to initialize config
*/
public static DatabaseConfig fromJson(String resource) throws DatabaseException {
try {
var url = DatabaseConfig.class.getResource(resource);
if (url == null) {
throw new RuntimeException("No config resource for path: " + resource);
} else {
var home = new File(System.getProperty("user.home"));
var keyPath = new File(home, ".d3x/keys/db.key").getAbsolutePath();
var keyFile = new File(System.getProperty("db.key", keyPath));
if (!keyFile.exists()) {
throw new RuntimeException("Missing cryptographic secret key at: " + keyFile.getAbsolutePath());
} else {
var keyUrl = keyFile.toURI().toURL();
var crypto = Crypto.withSecretKey("AES", keyUrl);
log.info("Loading database config from: " + resource);
return DatabaseConfig.fromJson(url, Option.of(crypto));
}
}
} catch (Exception ex) {
throw new DatabaseException("Failed to initialise database from config: " + resource, ex);
}
}
/**
* Writes this config as JSON to the output stream provided
* @param crypto the optional crypto for password encryption
* @param os the output stream to write to
*/
public void toJson(Option crypto, OutputStream os) throws IOException {
OutputStreamWriter writer = null;
try {
writer = new OutputStreamWriter(new BufferedOutputStream(os), StandardCharsets.UTF_8);
DatabaseConfig.gson(crypto).toJson(this, writer);
} finally {
if (writer != null) {
writer.flush();
writer.close();
}
}
}
/**
* Returns a new database config base on the consumer actions
* @param consumer the consumer to configure builder
* @return the newly created config
*/
public static DatabaseConfig of(Consumer consumer) {
var builder = DatabaseConfig.builder();
builder.readOnly(Option.of(false));
builder.autoCommit(Option.of(true));
builder.queryTimeOutSeconds(Option.of(60));
builder.initialPoolSize(Option.of(5));
builder.maxPoolSize(Option.of(10));
builder.maxWaitTimeMillis(Option.of(5000));
builder.maxPoolIdleSize(Option.of(10));
builder.fetchSize(Option.empty());
consumer.accept(builder);
return builder.build();
}
/**
* Returns a GSON adapter that can serialise DatabaseConfig
* @param crypto the optional crypto for password encryption
* @return the GSON instance
*/
public static Gson gson(Option crypto) {
var engine = new JsonEngine();
engine.register(schema, new Serializer());
engine.register(schema, new Deserializer());
if (crypto.isEmpty()) {
return engine.getGson(1);
} else {
engine.crypto(crypto.get());
return engine.getGson(1);
}
}
/**
* Attempts to connect to the database given this config to assess if it is valid
* @return this configuration object
* @throws DatabaseException if fails to make a connection
*/
public DatabaseConfig verify() {
Connection conn = null;
final String className = driver.getDriverClassName();
try {
log.info("Connecting to " + url);
Class.forName(className);
final String pass = password.map(p -> new String(p.getValue())).orNull();
conn = DriverManager.getConnection(url, user.orNull(), pass);
log.info("Connection successful to " + url);
return this;
} catch (ClassNotFoundException ex) {
throw new DatabaseException("Failed to load JDBC driver class " + className, ex);
} catch (SQLException ex) {
throw new DatabaseException("Failed to connect to database with " + url, ex);
} finally {
IO.close(conn);
}
}
public static class Serializer implements JsonSerializer {
@Override
public JsonElement serialize(DatabaseConfig record, Type type, JsonSerializationContext context) {
if (record == null) {
return JsonNull.INSTANCE;
} else {
return Json.object(object -> {
object.addProperty("driver", record.getDriver().getDriverClassName());
object.addProperty("url", record.getUrl());
object.addProperty("user", record.getUser().orNull());
object.add("password", context.serialize(record.getPassword().orNull(), Secret.class));
object.addProperty("initialPoolSize", record.initialPoolSize.orNull());
object.addProperty("maxPoolSize", record.maxPoolSize.orNull());
object.addProperty("maxPoolIdleSize", record.maxPoolIdleSize.orNull());
object.addProperty("readOnly", record.readOnly.orNull());
object.addProperty("autoCommit", record.autoCommit.orNull());
object.addProperty("queryTimeOutSeconds", record.queryTimeOutSeconds.orNull());
object.addProperty("maxWaitTimeMills", record.maxWaitTimeMillis.orNull());
object.addProperty("fetchSize", record.fetchSize.orNull());
object.add("properties", Json.object(props -> {
var keys = record.properties.keySet().stream().sorted();
keys.forEach(key -> {
var value = record.properties.get(key);
if (value != null) {
props.addProperty(key, value);
}
});
}));
});
}
}
}
public static class Deserializer implements JsonDeserializer {
@Override
public DatabaseConfig deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
if (json == null || json == JsonNull.INSTANCE) return null;
var object = json.getAsJsonObject();
var properties = new HashMap();
var propObject = Json.getObject(object, "properties").orElse(new JsonObject());
propObject.entrySet().forEach(entry -> {
var key = entry.getKey();
var value = entry.getValue().getAsString();
if (value != null) {
properties.put(key, value);
}
});
return DatabaseConfig.builder()
.driver(DatabaseDriver.of(Json.getStringOrFail(object, "driver")))
.url(Json.getStringOrFail(object, "url"))
.user(Json.getString(object, "user"))
.password(Json.getElement(object, "password").map(e -> context.deserialize(e, Secret.class)))
.initialPoolSize(Json.getInt(object, "initialPoolSize"))
.maxPoolSize(Json.getInt(object, "maxPoolSize"))
.maxPoolIdleSize(Json.getInt(object, "maxPoolIdleSize"))
.readOnly(Json.getBoolean(object, "readOnly"))
.autoCommit(Json.getBoolean(object, "autoCommit"))
.queryTimeOutSeconds(Json.getInt(object, "queryTimeOutSeconds"))
.maxWaitTimeMillis(Json.getInt(object, "maxWaitTimeMills"))
.fetchSize(Json.getInt(object, "fetchSize"))
.properties(properties)
.build();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy