Support parsing `Duration` values in `PropertyElf` (#2266)

* Add test for new property parsing

* Add implementation

* Add changelog item
pull/2271/head
Hidde Wieringa 2 months ago committed by GitHub
parent 84212307e4
commit 22eb91b9c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -3,6 +3,7 @@ HikariCP Changes
Changes in 6.2.2 Changes in 6.2.2
* increase keepaliveTime variance from 10% to 20% * 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 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 * 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. continue to fail.
* update test dependencies that were flagged as having vulnerabilities * update test dependencies that were flagged as having vulnerabilities

@ -20,7 +20,10 @@ import com.zaxxer.hikari.HikariConfig;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.time.Duration;
import java.util.*; import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* A class that reflectively sets bean properties on a target object. * A class that reflectively sets bean properties on a target object.
@ -29,6 +32,8 @@ import java.util.*;
*/ */
public final class PropertyElf public final class PropertyElf
{ {
private static final Pattern DURATION_PATTERN = Pattern.compile("^(?<number>\\d+)(?<unit>ms|s|m|h|d)$");
private PropertyElf() { private PropertyElf() {
// cannot be constructed // cannot be constructed
} }
@ -140,23 +145,24 @@ public final class PropertyElf
try { try {
var paramClass = writeMethod.getParameterTypes()[0]; var paramClass = writeMethod.getParameterTypes()[0];
String value = propValue.toString();
if (paramClass == int.class) { 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) { 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) { 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) { 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())) { 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) { else if (paramClass == String.class) {
writeMethod.invoke(target, propValue.toString()); writeMethod.invoke(target, value);
} }
else { else {
try { try {
@ -180,4 +186,29 @@ public final class PropertyElf
// use the english locale to avoid the infamous turkish locale bug // use the english locale to avoid the infamous turkish locale bug
return propertyName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propertyName.substring(1); return propertyName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propertyName.substring(1);
} }
private static Optional<Duration> 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();
}
}
} }

@ -19,11 +19,12 @@ package com.zaxxer.hikari.pool;
import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.time.Duration;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
@ -89,6 +90,27 @@ public class TestPropertySetter
PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties()); 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 @Test
public void testGetPropertyNames() throws Exception public void testGetPropertyNames() throws Exception
{ {
@ -99,14 +121,11 @@ public class TestPropertySetter
@Test @Test
public void testSetNonExistantPropertyName() throws Exception public void testSetNonExistantPropertyName() throws Exception
{ {
try { RuntimeException e = assertThrows(RuntimeException.class, () -> {
Properties props = new Properties(); Properties props = new Properties();
props.put("what", "happened"); props.put("what", "happened");
PropertyElf.setTargetFromProperties(new HikariConfig(), props); PropertyElf.setTargetFromProperties(new HikariConfig(), props);
fail(); });
} assertEquals("Property what does not exist on target class com.zaxxer.hikari.HikariConfig", e.getMessage());
catch (RuntimeException e) {
// fall-thru
}
} }
} }

@ -0,0 +1,8 @@
connectionTimeout = 11ms
validationTimeout = 22s
idleTimeout = 33m
leakDetectionThreshold = 44h
maxLifetime = 55d
dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource
dataSource.loginTimeout = 47m
Loading…
Cancel
Save