Skip to content

Commit

Permalink
Use ZoneId instead of String for timezone related configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
geoand committed Mar 3, 2021
1 parent d983423 commit 1f8dc00
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ public Holder(Class<F> from, Class<T> to, Class<ObjectSubstitution<F, T>> substi

public final Holder<?, ?> holder;

public <F, T> ObjectSubstitutionBuildItem(Class<F> from, Class<T> to, Class<ObjectSubstitution<F, T>> substitution) {
holder = new Holder<>(from, to, substitution);
public <F, T> ObjectSubstitutionBuildItem(Class<F> from, Class<T> to,
Class<? extends ObjectSubstitution<F, T>> substitution) {
holder = new Holder(from, to, substitution);
}

public ObjectSubstitutionBuildItem(Holder<?, ?> holder) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ObjectSubstitutionBuildItem> producer) {
zoneIdSubstitutions(producer);
}

@SuppressWarnings("unchecked")
private void zoneIdSubstitutions(BuildProducer<ObjectSubstitutionBuildItem> 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<ZoneId> zoneRegionClass = (Class<ZoneId>) Class.forName("java.time.ZoneRegion");
producer.produce(new ObjectSubstitutionBuildItem(zoneRegionClass, String.class, ZoneIdSubstitution.class));

Class<ZoneId> zoneOffsetClass = (Class<ZoneId>) 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<ZoneId>, Serializable {

private static final long serialVersionUID = -439010527617997936L;

@Override
public ZoneId convert(final String value) {
return ZoneId.of(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.runtime.recording.substitutions;

import java.time.ZoneId;

import io.quarkus.runtime.ObjectSubstitution;

public class ZoneIdSubstitution implements ObjectSubstitution<ZoneId, String> {

@Override
public String serialize(ZoneId obj) {
return obj.getId();
}

@Override
public ZoneId deserialize(String str) {
return ZoneId.of(str);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.jackson.runtime;

import java.time.ZoneId;
import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigItem;
Expand Down Expand Up @@ -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<String> timezone;
@ConfigItem(defaultValue = "UTC")
public Optional<ZoneId> timezone;
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,7 +24,7 @@ public boolean isWriteDatesAsTimestamps() {
return writeDatesAsTimestamps;
}

public String getTimeZone() {
public ZoneId getTimeZone() {
return timeZone;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -34,10 +35,9 @@ public ObjectMapper objectMapper(Instance<ObjectMapperCustomizer> 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<ObjectMapperCustomizer> sortedCustomizers = sortCustomizersInDescendingPriorityOrder(customizers);
for (ObjectMapperCustomizer customizer : sortedCustomizers) {
Expand Down

0 comments on commit 1f8dc00

Please sign in to comment.