Skip to content

Commit

Permalink
v1.2 better property-filter-prefixes support
Browse files Browse the repository at this point in the history
  • Loading branch information
magicprinc committed Mar 26, 2023
1 parent 7ceae29 commit 137bb70
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 69 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
group = com.github.magicprinc

version = 1.1.2
version = 1.2
Original file line number Diff line number Diff line change
Expand Up @@ -58,64 +58,72 @@ public class HikariEbeanDataSourcePool implements DataSourcePool {

final HikariDataSource ds;

public HikariEbeanDataSourcePool (HikariDataSource ds) {
this.ds = ds;
}//new
public HikariEbeanDataSourcePool (HikariDataSource ds){ this.ds = ds; }//new

public HikariEbeanDataSourcePool (String poolName, DataSourceConfig config) {
poolName = trim(poolName);
Map<String,String> aliasMap = alias();
public HikariEbeanDataSourcePool (String callerPoolName, DataSourceConfig config) {
String tmpTrimPoolName = trim(callerPoolName);
var defaultDatabaseName = determineDefaultServerName();
String hikariPoolName = tmpTrimPoolName.isEmpty() || tmpTrimPoolName.equals(defaultDatabaseName) ? "ebean"
: "ebean."+ tmpTrimPoolName;

Properties configProperties = Config.asProperties();
Properties sysProp = System.getProperties();
Properties dst = new Properties(configProperties.size() + 31);

filter(aliasMap, configProperties, dst, poolName);
filter(aliasMap, sysProp, dst, poolName);
Map<String,String> aliasMap = alias();
String[] prefixes = collectPrefixes(sysProp, configProperties);

String copyFromDb = trim(dst.getProperty("copyFrom"));
if (!copyFromDb.isEmpty()){// useful in tests: ^ workaround for main/test settings collisions
dst.clear();// mix of main and test configs
filter(aliasMap, configProperties, dst, copyFromDb);
filter(aliasMap, sysProp, dst, copyFromDb);
Properties dst = new Properties(configProperties.size() + 31);
{//1. search settings with our db_name
String databaseName = makeDatabaseNamePrefix(tmpTrimPoolName, defaultDatabaseName, sysProp, configProperties);
filter(aliasMap, configProperties, dst, databaseName, prefixes);
filter(aliasMap, sysProp, dst, databaseName, prefixes);// System.properties overwrite file.properties
}
String[] appendFrom = trim(dst.getProperty("appendFrom")).split("[;,]");
for (String db : appendFrom){// add settings from another db
db = trim(db);
if (!db.isEmpty()){
filter(aliasMap, configProperties, dst, db);
filter(aliasMap, sysProp, dst, db);
{//2. use settings of another db_name
String copyFromDb = trim(dst.getProperty("copyFrom"));
if (!copyFromDb.isEmpty()){// useful in tests: ^ workaround for main/test settings collisions
dst.clear();// mix of main and test configs
filter(aliasMap, configProperties, dst, copyFromDb, prefixes);
filter(aliasMap, sysProp, dst, copyFromDb, prefixes);
}
}

String confFile = trim(dst.getProperty("confFile"));
if (!confFile.isEmpty()){// load from another ^ <hikari>.properties file: full delegation!
ds = createDataSource(new HikariConfig(confFile), poolName);
return;
{//3. add settings from "template" db_name
String[] appendFrom = trim(dst.getProperty("appendFrom")).split("[;,]");
for (String db : appendFrom){// add settings from another db
db = trim(db);
if (!db.isEmpty()){
filter(aliasMap, configProperties, dst, db, prefixes);
filter(aliasMap, sysProp, dst, db, prefixes);
}
}
}
{//4. load from another <hikari>.properties file == full delegation!
String confFile = trim(dst.getProperty("confFile"));
if (!confFile.isEmpty()){
ds = createDataSource(new HikariConfig(confFile), hikariPoolName);
return;
}
}

HikariConfig hc = new HikariConfig();
hc.setPoolName(poolName.isEmpty() ? "ebean" : "ebean."+ poolName);// helps to know poolName during init phase
hc.setPoolName(hikariPoolName);// helps to know poolName during init phase
String propertyNames = normValue(trim(dst.getProperty("propertyNames")))
.replace("-","").replace("then","").replace("only","").replace("than","");
// hikari-ebean, ebean-hikari (default), hikari, ebean
if ("hikari".equalsIgnoreCase(propertyNames)){

if ("hikari".equalsIgnoreCase(propertyNames)){// hikari only
setTargetFromProperties(hc, dst);
ds = createDataSource(hc, poolName);// only hikari property names are used
ds = createDataSource(hc, hikariPoolName);// only hikari property names are used

} else if ("hikariebean".equalsIgnoreCase(propertyNames)){
} else if ("hikariebean".equalsIgnoreCase(propertyNames)){// hikari-then-ebean
setTargetFromProperties(hc, dst);//1. hikari
mergeFromDataSourceConfig(hc, config);//2. ebean (overrides)
ds = createDataSource(hc, poolName);
ds = createDataSource(hc, hikariPoolName);

} else if ("ebean".equalsIgnoreCase(propertyNames)){
} else if ("ebean".equalsIgnoreCase(propertyNames)){// ebean only
mergeFromDataSourceConfig(hc, config);
ds = createDataSource(hc, poolName);
ds = createDataSource(hc, hikariPoolName);

} else {// everything else: ebean-hikari
} else {// everything else: ebean-hikari (default)
mergeFromDataSourceConfig(hc, config);//1.ebean
setTargetFromProperties(hc, dst);//2.hikari (overrides)
ds = createDataSource(hc, poolName);
ds = createDataSource(hc, hikariPoolName);
}
}//new

Expand All @@ -125,15 +133,56 @@ protected HikariDataSource createDataSource (HikariConfig hc, String poolName){
} catch (Throwable ignore){
// no Micrometer in classPath; see also hikariConfig.setRegisterMbeans(true)
}
hc.setPoolName(poolName.isEmpty() ? "ebean" : "ebean."+ poolName);
hc.setPoolName(poolName);
return new HikariDataSource(hc);
}

/** Default database has no name: "". What prefix should we use in config to filter/find its settings (default: db.)
If you choose empty "" name: it will disable "db_name.property_name" keys. */
protected String makeDatabaseNamePrefix (String trimCallerPoolName, String defaultDatabaseName, Properties... sysPropThenConfig){
if (trimCallerPoolName.isEmpty() || trimCallerPoolName.equals(defaultDatabaseName)){// default database
for (var p : sysPropThenConfig){

for (var e : p.entrySet()){
if ("ebean.hikari.defaultdb".equals(stripKey(e.getKey()))){
var db = trim(e.getValue());
return db.isEmpty() ? "" : db + '.';
}
}
}
return defaultDatabaseName;// default prefix for default database (usually db)

} else {// named database e.g: my-DB_Name
return trimCallerPoolName + '.';//e.g: my-db_name.
}
}

/**
* Determine and return the default server name checking system environment variables and then global properties.
* @see io.ebean.DbPrimary#determineDefaultServerName
*/
private static String determineDefaultServerName () {
String defaultServerName = System.getenv("EBEAN_DB");
defaultServerName = System.getProperty("db", defaultServerName);
defaultServerName = System.getProperty("ebean_db", defaultServerName);
if (trim(defaultServerName).isEmpty()) {
defaultServerName = Config.getOptional("datasource.default").orElse(null);
if (trim(defaultServerName).isEmpty()) {
defaultServerName = Config.getOptional("ebean.default.datasource").orElse(null);
}
}
if (defaultServerName == null) {
defaultServerName = "db";
}
return trim(defaultServerName);
}

/** sub-prefix for real-vendor-jdbc-driver settings (e.g. MSSQL statementPoolingCacheSize)*/
private static final String[] VENDOR_SETTINGS_PREFIX = {"datasource.", "driver."};

/** @see PropertyElf#setTargetFromProperties*/
static void setTargetFromProperties (HikariConfig hc, Properties p){
p.remove("appendFrom"); p.remove("copyFrom"); p.remove("confFile");
p.remove("appendFrom"); p.remove("copyFrom"); p.remove("confFile"); p.remove("propertyNames");

List<Method> methods = Arrays.asList(hc.getClass().getMethods());
p.forEach((keyObj, value) -> {
Expand Down Expand Up @@ -245,23 +294,24 @@ protected void mergeFromDataSourceConfig (HikariConfig hc, DataSourceConfig dsc)
protected Map<String,String> alias (){
try {
InputStream is = getClass().getResourceAsStream("/ebean/ehpalias.properties");
if (is == null){
return Collections.emptyMap();
}
if (is == null){ return Collections.emptyMap(); }// can't be! There is the file!
try (is){
Properties p = new Properties();
p.load(new InputStreamReader(is, StandardCharsets.UTF_8));
HashMap<String,String> m = new HashMap<>(p.size()+31);
p.forEach((keyObj,valueObj)->{
String key = trim(keyObj).toLowerCase(Locale.ENGLISH).replace("-", "").replace("_", "");
m.put(key, trim(valueObj));
});
for (var e : p.entrySet()){
m.put(stripKey(e.getKey()), trim(e.getValue()));
}
return m;
}
} catch (Throwable ignore){
return Collections.emptyMap();
}
}
/** trim, lowerCase(EN), remove - and _ */
private String stripKey (Object key){
return trim(key).toLowerCase(Locale.ENGLISH).replace("-", "").replace("_", "");
}

void sets (String dataSourceConfig, Consumer<String> setter){
String s = trim(dataSourceConfig);
Expand Down Expand Up @@ -311,9 +361,23 @@ static String normValue (Object value) {
}
return s;
}

private static final Pattern SPACE_AND_UNDERSCORE = Pattern.compile("[\\s_]", Pattern.CASE_INSENSITIVE|Pattern.UNICODE_CHARACTER_CLASS);
static final String[] SETTINGS_PREFIX = { "%db%", System.getProperty("hibean.prefix", "datasource.%db%"), "spring.datasource.%db%hikari." };

/** Prefixes to filter our (hikari) property names. Default: db. → datasource.db → spring.datasource.db.hikari. */
protected String[] collectPrefixes (Properties sysProp, Properties config){
String[] p = new String[10];
p[1] = "%db%";
p[3] = "datasource.%db%";
p[5] = "spring.datasource.%db%hikari.";
p[7] = sysProp.getProperty("ebean.hikari.prefix", config.getProperty("ebean.hikari.prefix"));

for (int i=0; i<p.length; i++){
String key = "ebean.hikari.prefix."+i;
p[i] = trim(sysProp.getProperty(key, config.getProperty(key, p[i]))).toLowerCase(Locale.ENGLISH);
}
Arrays.sort(p, Comparator.comparing(String::length).reversed());
return p;
}

/**
* Filter very wide application.properties using the prefix and db name (if supplied) to search for the hikari properties.
Expand All @@ -325,10 +389,13 @@ static String normValue (Object value) {
*
* }</pre>
*/
void filter (Map<String,String> aliasMap, Properties src, Properties dst, String dbName) {
String db = dbName.isEmpty() ? "db." : dbName.toLowerCase()+'.'; // db. or mydb.
var prefixes = Arrays.stream(SETTINGS_PREFIX)
.map(pre->trim(pre).toLowerCase(Locale.ENGLISH).replace("%db%", db))
void filter (Map<String,String> aliasMap, Properties src, Properties dst, String dbName, String[] prefixTemplates){
dbName = trim(dbName).toLowerCase(Locale.ENGLISH);
final String db = dbName + (dbName.isEmpty() || dbName.endsWith(".") ? "" : ".");

var prefixes = Arrays.stream(prefixTemplates)
.map(pre -> trim(pre).toLowerCase(Locale.ENGLISH).replace("%db%", db))
.filter(pre -> !pre.isEmpty())
.sorted(Comparator.comparing(String::length).reversed()).toArray(String[]::new);

src.forEach((keyObj,value)->{// datasource.db.url = jdbc:h2:mem:testMix
Expand Down Expand Up @@ -357,19 +424,14 @@ void filter (Map<String,String> aliasMap, Properties src, Properties dst, String
});
}

@Override public String name () {
return ds.getPoolName();
}
@Override public String name () { return ds.getPoolName(); }

@Override public int size () {
HikariPoolMXBean hPool = ds.getHikariPoolMXBean();
return hPool.getTotalConnections();
}


@Override public boolean isAutoCommit () {
return ds.isAutoCommit();
}
@Override public boolean isAutoCommit () { return ds.isAutoCommit(); }

@Override public boolean isOnline () {
return ds.isRunning() && !ds.isClosed();
Expand Down Expand Up @@ -424,10 +486,7 @@ void filter (Map<String,String> aliasMap, Properties src, Properties dst, String
};
}


@Override public SQLException dataSourceDownReason () {
return null;
}
@Override public SQLException dataSourceDownReason () { return null; }

@Override public void setMaxSize (int max) {
HikariConfigMXBean cfg = ds.getHikariConfigMXBean();
Expand Down
5 changes: 4 additions & 1 deletion src/main/resources/ebean/ehpalias.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
# custom (ebean) property name = hikari property name
url = jdbcUrl
databaseUrl = jdbcUrl
maxConnections = maximumPoolSize
maxConnections = maximumPoolSize
driver = driverClassName

maxsize = maximumPoolSize
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,8 @@ class HikariEbeanDataSourcePoolTest {


@Test void testAnotherPrefix () {
var save = HikariEbeanDataSourcePool.SETTINGS_PREFIX.clone();
try {
HikariEbeanDataSourcePool.SETTINGS_PREFIX[1] = "spring.datasource.";
System.setProperty("ebean.hikari.prefix", "spring.datasource.");

var db = DB.byName("test_spring");
var pool = (HikariEbeanDataSourcePool) db.dataSource();
Expand All @@ -106,8 +105,43 @@ class HikariEbeanDataSourcePoolTest {
assertTrue(pool.ds.isReadOnly());
assertEquals(54321, pool.ds.getMaxLifetime());

System.setProperty("ebean.hikari.prefix", "quarkus.datasource.");
System.setProperty("ebean.hikari.default-Db", "");
var dataSourcePool = (HikariEbeanDataSourcePool) new HikariEbeanConnectionPoolFactory().createPool(null, new DataSourceConfig());
assertEquals("jdbc:h2:mem:evenQuarkus", dataSourcePool.ds.getJdbcUrl());
assertEquals("org.h2.Driver", dataSourcePool.ds.getDriverClassName());
assertEquals("test", dataSourcePool.ds.getUsername());
assertEquals("1234", dataSourcePool.ds.getPassword());
assertEquals(93, dataSourcePool.ds.getMaximumPoolSize());

} finally {
System.arraycopy(save, 0, HikariEbeanDataSourcePool.SETTINGS_PREFIX, 0, save.length);
Properties p = System.getProperties();
p.remove("ebean.hikari.prefix");
p.remove("ebean.hikari.default-Db");
}
}


@Test void _emptyPropertyIsNotAbsentProperty () {
System.setProperty("fake_empty_prop", "");
assertTrue(System.getProperties().containsKey("fake_empty_prop"));
assertEquals("", System.getProperty("fake_empty_prop"));
assertEquals("", System.getProperty("fake_empty_prop", "default"));
}


@Test void _checkDefaultDbProperties () {
var ds = (HikariEbeanDataSourcePool) DB.getDefault().dataSource();
assertEquals("jdbc:h2:mem:my_app", ds.ds.getJdbcUrl());// from ebean-test platform
assertEquals("org.h2.Driver", ds.ds.getDriverClassName());
assertNull(ds.ds.getDataSourceClassName());
assertEquals("sa", ds.ds.getUsername());
assertNull(ds.ds.getPassword());
assertEquals("ebean", ds.ds.getPoolName());
assertEquals(1_800_000, ds.ds.getMaxLifetime());// default 30 mi
assertEquals(31, ds.ds.getMaximumPoolSize());
assertEquals(2, ds.ds.getMinimumIdle());
assertEquals(61_000, ds.ds.getIdleTimeout());
assertNull(ds.ds.getConnectionInitSql());
}
}
8 changes: 7 additions & 1 deletion src/test/resources/application-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,10 @@ spring:

spring.DataSource.test_spring.HiKari:
max-lifetime: 54321
ReadOnly: true
ReadOnly: true

quarkus.datasource.url: jdbc:h2:mem:evenQuarkus
quarkus.datasource.driver: org.h2.Driver
quarkus.datasource.username: test
quarkus.datasource.password: 1234
quarkus.datasource.max-size: 93

0 comments on commit 137bb70

Please sign in to comment.