From 1f8dc00b1b7640980c867ad3366dd2bbf3af1d85 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Mar 2021 13:52:33 +0200 Subject: [PATCH] Use ZoneId instead of String for timezone related configuration --- .../ObjectSubstitutionBuildItem.java | 5 +-- .../AdditionalSubstitutionsBuildStep.java | 34 +++++++++++++++++++ .../configuration/ZoneIdConverter.java | 24 +++++++++++++ .../substitutions/ZoneIdSubstitution.java | 18 ++++++++++ ....eclipse.microprofile.config.spi.Converter | 1 + .../jackson/deployment/JacksonProcessor.java | 18 ---------- ...acksonErroneousTimeZonePropertiesTest.java | 3 +- .../runtime/JacksonBuildTimeConfig.java | 5 +-- .../jackson/runtime/JacksonConfigSupport.java | 8 +++-- .../jackson/runtime/ObjectMapperProducer.java | 8 ++--- 10 files changed, 94 insertions(+), 30 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/recording/substitutions/AdditionalSubstitutionsBuildStep.java create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/ZoneIdConverter.java create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/recording/substitutions/ZoneIdSubstitution.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ObjectSubstitutionBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ObjectSubstitutionBuildItem.java index ef465c6f3a84a7..92d3b8602d1d25 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ObjectSubstitutionBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ObjectSubstitutionBuildItem.java @@ -30,8 +30,9 @@ public Holder(Class from, Class to, Class> substi public final Holder holder; - public ObjectSubstitutionBuildItem(Class from, Class to, Class> substitution) { - holder = new Holder<>(from, to, substitution); + public ObjectSubstitutionBuildItem(Class from, Class to, + Class> substitution) { + holder = new Holder(from, to, substitution); } public ObjectSubstitutionBuildItem(Holder holder) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/substitutions/AdditionalSubstitutionsBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/substitutions/AdditionalSubstitutionsBuildStep.java new file mode 100644 index 00000000000000..c4182eac9a96d4 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/substitutions/AdditionalSubstitutionsBuildStep.java @@ -0,0 +1,34 @@ +package io.quarkus.deployment.recording.substitutions; + +import java.time.ZoneId; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem; +import io.quarkus.runtime.recording.substitutions.ZoneIdSubstitution; + +public class AdditionalSubstitutionsBuildStep { + + @BuildStep + public void additionalSubstitutions(BuildProducer producer) { + zoneIdSubstitutions(producer); + } + + @SuppressWarnings("unchecked") + private void zoneIdSubstitutions(BuildProducer producer) { + try { + /* + * We can't refer to these classes as they are package private but we need a handle on need + * because the bytecode recorder needs to have the actual class registered and not a super class + */ + + Class zoneRegionClass = (Class) Class.forName("java.time.ZoneRegion"); + producer.produce(new ObjectSubstitutionBuildItem(zoneRegionClass, String.class, ZoneIdSubstitution.class)); + + Class zoneOffsetClass = (Class) Class.forName("java.time.ZoneOffset"); + producer.produce(new ObjectSubstitutionBuildItem(zoneOffsetClass, String.class, ZoneIdSubstitution.class)); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Improper registration of ZoneId substitution", e); + } + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ZoneIdConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ZoneIdConverter.java new file mode 100644 index 00000000000000..d584e2d72ccf62 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ZoneIdConverter.java @@ -0,0 +1,24 @@ +package io.quarkus.runtime.configuration; + +import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; + +import java.io.Serializable; +import java.time.ZoneId; + +import javax.annotation.Priority; + +import org.eclipse.microprofile.config.spi.Converter; + +/** + * A converter to support locales. + */ +@Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) +public class ZoneIdConverter implements Converter, Serializable { + + private static final long serialVersionUID = -439010527617997936L; + + @Override + public ZoneId convert(final String value) { + return ZoneId.of(value); + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/recording/substitutions/ZoneIdSubstitution.java b/core/runtime/src/main/java/io/quarkus/runtime/recording/substitutions/ZoneIdSubstitution.java new file mode 100644 index 00000000000000..9e9f28cfe2875c --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/recording/substitutions/ZoneIdSubstitution.java @@ -0,0 +1,18 @@ +package io.quarkus.runtime.recording.substitutions; + +import java.time.ZoneId; + +import io.quarkus.runtime.ObjectSubstitution; + +public class ZoneIdSubstitution implements ObjectSubstitution { + + @Override + public String serialize(ZoneId obj) { + return obj.getId(); + } + + @Override + public ZoneId deserialize(String str) { + return ZoneId.of(str); + } +} diff --git a/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter b/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter index 9c6a04c4ca0b86..7a9cef122ded4b 100644 --- a/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter +++ b/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter @@ -7,4 +7,5 @@ io.quarkus.runtime.configuration.PathConverter io.quarkus.runtime.configuration.DurationConverter io.quarkus.runtime.configuration.MemorySizeConverter io.quarkus.runtime.configuration.LocaleConverter +io.quarkus.runtime.configuration.ZoneIdConverter io.quarkus.runtime.logging.LevelConverter diff --git a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java index 88a544abe5ae64..4b0d32c66a07a8 100755 --- a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java +++ b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java @@ -7,7 +7,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.TimeZone; import javax.inject.Inject; import javax.inject.Singleton; @@ -179,23 +178,6 @@ private void registerModuleIfOnClassPath(String moduleClassName, @Record(ExecutionTime.STATIC_INIT) SyntheticBeanBuildItem pushConfigurationBean(JacksonRecorder jacksonRecorder, JacksonBuildTimeConfig jacksonBuildTimeConfig) { - - if (jacksonBuildTimeConfig.timezone.isPresent()) { - /* - * We need to make timezone a String instead of a java.util.TimeZone class - * because: - * 1) TimeZone cannot automatically be handled by the BytecodeRecorder - * 2) Handling it would require us to add non-JDK classes (i.e. sun.util.calendar.ZoneInfo) to bytecode recording - */ - String timeZoneStr = jacksonBuildTimeConfig.timezone.get(); - TimeZone timeZone = TimeZone.getTimeZone(timeZoneStr); - if ("GMT".equals(timeZone.getID()) && !timeZoneStr.startsWith("GMT")) { - // Parsing an illegal TZ string value results in falling back to GMT... - throw new IllegalArgumentException( - "Value '" + timeZoneStr + "' is an invalid value for the 'quarkus.jackson.timezone' property"); - } - } - return SyntheticBeanBuildItem.configure(JacksonConfigSupport.class) .scope(Singleton.class) .supplier(jacksonRecorder.jacksonConfigSupport(jacksonBuildTimeConfig)) diff --git a/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/JacksonErroneousTimeZonePropertiesTest.java b/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/JacksonErroneousTimeZonePropertiesTest.java index ba855c82371634..68cee40b3f3031 100644 --- a/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/JacksonErroneousTimeZonePropertiesTest.java +++ b/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/JacksonErroneousTimeZonePropertiesTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.fail; +import java.time.zone.ZoneRulesException; import java.util.Date; import javax.inject.Inject; @@ -23,7 +24,7 @@ public class JacksonErroneousTimeZonePropertiesTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(Pojo.class, SomeBean.class)) .withConfigurationResource("application-erroneous-timezone-properties.properties") - .setExpectedException(IllegalArgumentException.class); + .setExpectedException(ZoneRulesException.class); @Test public void test() { diff --git a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/JacksonBuildTimeConfig.java b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/JacksonBuildTimeConfig.java index aae6c1b716559b..5ff4cd869cc2e6 100644 --- a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/JacksonBuildTimeConfig.java +++ b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/JacksonBuildTimeConfig.java @@ -1,5 +1,6 @@ package io.quarkus.jackson.runtime; +import java.time.ZoneId; import java.util.Optional; import io.quarkus.runtime.annotations.ConfigItem; @@ -27,6 +28,6 @@ public class JacksonBuildTimeConfig { * Some examples values are "Asia/Jakarta" and "GMT+3". * If not set, Jackson will use its own default. */ - @ConfigItem - public Optional timezone; + @ConfigItem(defaultValue = "UTC") + public Optional timezone; } diff --git a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/JacksonConfigSupport.java b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/JacksonConfigSupport.java index 75cc56e52519d2..82ba9ffcbe55a4 100644 --- a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/JacksonConfigSupport.java +++ b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/JacksonConfigSupport.java @@ -1,14 +1,16 @@ package io.quarkus.jackson.runtime; +import java.time.ZoneId; + public class JacksonConfigSupport { private final boolean failOnUnknownProperties; private final boolean writeDatesAsTimestamps; - private final String timeZone; + private final ZoneId timeZone; - public JacksonConfigSupport(boolean failOnUnknownProperties, boolean writeDatesAsTimestamps, String timeZone) { + public JacksonConfigSupport(boolean failOnUnknownProperties, boolean writeDatesAsTimestamps, ZoneId timeZone) { this.failOnUnknownProperties = failOnUnknownProperties; this.writeDatesAsTimestamps = writeDatesAsTimestamps; this.timeZone = timeZone; @@ -22,7 +24,7 @@ public boolean isWriteDatesAsTimestamps() { return writeDatesAsTimestamps; } - public String getTimeZone() { + public ZoneId getTimeZone() { return timeZone; } } diff --git a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java index 16f4fa74554eae..4d9b3cd4f5e5aa 100644 --- a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java +++ b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java @@ -1,5 +1,6 @@ package io.quarkus.jackson.runtime; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -34,10 +35,9 @@ public ObjectMapper objectMapper(Instance customizers, // this feature is enabled by default, so we disable it objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } - String timeZoneStr = jacksonConfigSupport.getTimeZone(); - if (timeZoneStr != null) { - TimeZone timeZone = TimeZone.getTimeZone(timeZoneStr); - objectMapper.setTimeZone(timeZone); + ZoneId zoneId = jacksonConfigSupport.getTimeZone(); + if ((zoneId != null) && !zoneId.getId().equals("UTC")) { // Jackson uses UTC as the default, so let's not reset it + objectMapper.setTimeZone(TimeZone.getTimeZone(zoneId)); } List sortedCustomizers = sortCustomizersInDescendingPriorityOrder(customizers); for (ObjectMapperCustomizer customizer : sortedCustomizers) {