From 137bb70e52d3e307df3af74e074078575f05734e Mon Sep 17 00:00:00 2001 From: "A.Fink" Date: Mon, 27 Mar 2023 00:18:44 +0300 Subject: [PATCH] v1.2 better property-filter-prefixes support --- gradle.properties | 2 +- .../hibean/HikariEbeanDataSourcePool.java | 185 ++++++++++++------ src/main/resources/ebean/ehpalias.properties | 5 +- .../hibean/HikariEbeanDataSourcePoolTest.java | 40 +++- src/test/resources/application-test.yaml | 8 +- 5 files changed, 171 insertions(+), 69 deletions(-) diff --git a/gradle.properties b/gradle.properties index bcf9b66..1018812 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ group = com.github.magicprinc -version = 1.1.2 \ No newline at end of file +version = 1.2 \ No newline at end of file diff --git a/src/main/java/com/github/magicprinc/hibean/HikariEbeanDataSourcePool.java b/src/main/java/com/github/magicprinc/hibean/HikariEbeanDataSourcePool.java index e439f24..f8b6666 100644 --- a/src/main/java/com/github/magicprinc/hibean/HikariEbeanDataSourcePool.java +++ b/src/main/java/com/github/magicprinc/hibean/HikariEbeanDataSourcePool.java @@ -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 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 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 ^ .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 .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 @@ -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 methods = Arrays.asList(hc.getClass().getMethods()); p.forEach((keyObj, value) -> { @@ -245,23 +294,24 @@ protected void mergeFromDataSourceConfig (HikariConfig hc, DataSourceConfig dsc) protected Map 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 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 setter){ String s = trim(dataSourceConfig); @@ -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 */ - void filter (Map 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 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 @@ -357,19 +424,14 @@ void filter (Map 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(); @@ -424,10 +486,7 @@ void filter (Map 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(); diff --git a/src/main/resources/ebean/ehpalias.properties b/src/main/resources/ebean/ehpalias.properties index 88e25b2..f351434 100644 --- a/src/main/resources/ebean/ehpalias.properties +++ b/src/main/resources/ebean/ehpalias.properties @@ -2,4 +2,7 @@ # custom (ebean) property name = hikari property name url = jdbcUrl databaseUrl = jdbcUrl -maxConnections = maximumPoolSize \ No newline at end of file +maxConnections = maximumPoolSize +driver = driverClassName + +maxsize = maximumPoolSize \ No newline at end of file diff --git a/src/test/java/com/github/magicprinc/hibean/HikariEbeanDataSourcePoolTest.java b/src/test/java/com/github/magicprinc/hibean/HikariEbeanDataSourcePoolTest.java index c1ee3a2..49f8ca2 100644 --- a/src/test/java/com/github/magicprinc/hibean/HikariEbeanDataSourcePoolTest.java +++ b/src/test/java/com/github/magicprinc/hibean/HikariEbeanDataSourcePoolTest.java @@ -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(); @@ -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()); + } } \ No newline at end of file diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml index 9be9088..b66f727 100644 --- a/src/test/resources/application-test.yaml +++ b/src/test/resources/application-test.yaml @@ -47,4 +47,10 @@ spring: spring.DataSource.test_spring.HiKari: max-lifetime: 54321 - ReadOnly: true \ No newline at end of file + 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 \ No newline at end of file