diff --git a/CHANGES b/CHANGES index 12c897dbe..e5c139586 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ HikariCP Changes Changes in 6.2.2 * increase keepaliveTime variance from 10% to 20% + * support duration values for configuration from properties, such as 10ms, 20s, 30m, 40h or 50d Changes in 6.2.1 @@ -17,7 +18,7 @@ Changes in 6.2.0 * added new enum value, Override.MUST_EVICT, available to implementations of com.zaxxer.hikari.SQLExceptionOverride - * enhanced debug logging in circumstances where the pool falls to zero size and new coonections to the database + * enhanced debug logging in circumstances where the pool falls to zero size and new connections to the database continue to fail. * update test dependencies that were flagged as having vulnerabilities diff --git a/src/main/java/com/zaxxer/hikari/util/PropertyElf.java b/src/main/java/com/zaxxer/hikari/util/PropertyElf.java index a7825b5eb..7b912cba9 100644 --- a/src/main/java/com/zaxxer/hikari/util/PropertyElf.java +++ b/src/main/java/com/zaxxer/hikari/util/PropertyElf.java @@ -20,7 +20,10 @@ import org.slf4j.LoggerFactory; import java.lang.reflect.Method; +import java.time.Duration; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * A class that reflectively sets bean properties on a target object. @@ -29,6 +32,8 @@ */ public final class PropertyElf { + private static final Pattern DURATION_PATTERN = Pattern.compile("^(?\\d+)(?ms|s|m|h|d)$"); + private PropertyElf() { // cannot be constructed } @@ -140,23 +145,24 @@ private static void setProperty(final Object target, final String propName, fina try { var paramClass = writeMethod.getParameterTypes()[0]; + String value = propValue.toString(); if (paramClass == int.class) { - writeMethod.invoke(target, Integer.parseInt(propValue.toString())); + writeMethod.invoke(target, parseDuration(value).map(duration -> (int) duration.toMillis()).orElseGet(() -> Integer.parseInt(value))); } else if (paramClass == long.class) { - writeMethod.invoke(target, Long.parseLong(propValue.toString())); + writeMethod.invoke(target, parseDuration(value).map(Duration::toMillis).orElseGet(() -> Long.parseLong(value))); } else if (paramClass == short.class) { - writeMethod.invoke(target, Short.parseShort(propValue.toString())); + writeMethod.invoke(target, Short.parseShort(value)); } else if (paramClass == boolean.class || paramClass == Boolean.class) { - writeMethod.invoke(target, Boolean.parseBoolean(propValue.toString())); + writeMethod.invoke(target, Boolean.parseBoolean(value)); } else if (paramClass.isArray() && char.class.isAssignableFrom(paramClass.getComponentType())) { - writeMethod.invoke(target, propValue.toString().toCharArray()); + writeMethod.invoke(target, value.toCharArray()); } else if (paramClass == String.class) { - writeMethod.invoke(target, propValue.toString()); + writeMethod.invoke(target, value); } else { try { @@ -180,4 +186,29 @@ private static String capitalizedPropertyName(String propertyName) // use the english locale to avoid the infamous turkish locale bug return propertyName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propertyName.substring(1); } + + private static Optional parseDuration(String value) + { + Matcher matcher = DURATION_PATTERN.matcher(value); + if (matcher.matches()) { + long number = Long.parseLong(matcher.group("number")); + String unit = matcher.group("unit"); + switch (unit) { + case "ms": + return Optional.of(Duration.ofMillis(number)); + case "s": + return Optional.of(Duration.ofSeconds(number)); + case "m": + return Optional.of(Duration.ofMinutes(number)); + case "h": + return Optional.of(Duration.ofHours(number)); + case "d": + return Optional.of(Duration.ofDays(number)); + default: + throw new IllegalStateException(String.format("Could not match unit, got %s (from given value %s)", unit, value)); + } + } else { + return Optional.empty(); + } + } } diff --git a/src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java b/src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java index 2a8f49849..bc0954118 100644 --- a/src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java +++ b/src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java @@ -19,11 +19,12 @@ import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; +import java.time.Duration; import java.util.Properties; import java.util.Set; @@ -89,6 +90,27 @@ public void testPropertyUpperCase() throws Exception PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties()); } + @Test + public void testDurationPropertiesSet() throws Exception + { + Properties durationProperties = new Properties(); + durationProperties.load(TestPropertySetter.class.getResourceAsStream("/duration-config.properties")); + HikariConfig config = new HikariConfig(durationProperties); + config.validate(); + + assertEquals(Duration.ofMillis(11), Duration.ofMillis(config.getConnectionTimeout())); + assertEquals(Duration.ofSeconds(22), Duration.ofMillis(config.getValidationTimeout())); + assertEquals(Duration.ofMinutes(33), Duration.ofMillis(config.getIdleTimeout())); + assertEquals(Duration.ofHours(44), Duration.ofMillis(config.getLeakDetectionThreshold())); + assertEquals(Duration.ofDays(55), Duration.ofMillis(config.getMaxLifetime())); + + Class clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName()); + DataSource dataSource = (DataSource) clazz.getDeclaredConstructor().newInstance(); + PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties()); + + assertEquals(Duration.ofMinutes(47), Duration.ofMillis(dataSource.getLoginTimeout())); + } + @Test public void testGetPropertyNames() throws Exception { @@ -99,14 +121,11 @@ public void testGetPropertyNames() throws Exception @Test public void testSetNonExistantPropertyName() throws Exception { - try { + RuntimeException e = assertThrows(RuntimeException.class, () -> { Properties props = new Properties(); props.put("what", "happened"); PropertyElf.setTargetFromProperties(new HikariConfig(), props); - fail(); - } - catch (RuntimeException e) { - // fall-thru - } + }); + assertEquals("Property what does not exist on target class com.zaxxer.hikari.HikariConfig", e.getMessage()); } } diff --git a/src/test/resources/duration-config.properties b/src/test/resources/duration-config.properties new file mode 100644 index 000000000..bc90571ec --- /dev/null +++ b/src/test/resources/duration-config.properties @@ -0,0 +1,8 @@ +connectionTimeout = 11ms +validationTimeout = 22s +idleTimeout = 33m +leakDetectionThreshold = 44h +maxLifetime = 55d + +dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource +dataSource.loginTimeout = 47m