diff --git a/core/src/main/java/google/registry/persistence/DateTimeConverter.java b/core/src/main/java/google/registry/persistence/DateTimeConverter.java new file mode 100644 index 000000000..ecd9048ee --- /dev/null +++ b/core/src/main/java/google/registry/persistence/DateTimeConverter.java @@ -0,0 +1,41 @@ +// Copyright 2019 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.persistence; + +import static org.joda.time.DateTimeZone.UTC; + +import java.sql.Timestamp; +import javax.annotation.Nullable; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; +import org.joda.time.DateTime; + +/** JPA converter to for storing/retrieving {@link org.joda.time.DateTime} objects. */ +@Converter(autoApply = true) +public class DateTimeConverter implements AttributeConverter { + + @Override + @Nullable + public Timestamp convertToDatabaseColumn(@Nullable DateTime attribute) { + return attribute == null ? null : new Timestamp(attribute.getMillis()); + } + + @Override + @Nullable + public DateTime convertToEntityAttribute(@Nullable Timestamp dbData) { + DateTime result = dbData == null ? null : new DateTime(dbData.getTime(), UTC); + return result; + } +} diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index 7faf856c6..10a931a02 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -36,6 +36,7 @@ google.registry.persistence.BloomFilterConverter google.registry.persistence.CreateAutoTimestampConverter google.registry.persistence.CurrencyUnitConverter + google.registry.persistence.DateTimeConverter google.registry.persistence.UpdateAutoTimestampConverter google.registry.persistence.ZonedDateTimeConverter diff --git a/core/src/test/java/google/registry/persistence/DateTimeConverterTest.java b/core/src/test/java/google/registry/persistence/DateTimeConverterTest.java new file mode 100644 index 000000000..09df43797 --- /dev/null +++ b/core/src/test/java/google/registry/persistence/DateTimeConverterTest.java @@ -0,0 +1,107 @@ +// Copyright 2019 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.persistence; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.transaction.TransactionManagerFactory.jpaTm; + +import google.registry.model.ImmutableObject; +import google.registry.model.transaction.JpaTransactionManagerRule; +import java.sql.Timestamp; +import javax.persistence.Entity; +import javax.persistence.Id; +import org.joda.time.DateTime; +import org.joda.time.format.ISODateTimeFormat; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link DateTimeConverter}. */ +@RunWith(JUnit4.class) +public class DateTimeConverterTest { + + @Rule + public final JpaTransactionManagerRule jpaTmRule = + new JpaTransactionManagerRule.Builder().withEntityClass(TestEntity.class).build(); + + private final DateTimeConverter converter = new DateTimeConverter(); + + @Test + public void convertToDatabaseColumn_returnsNullIfInputIsNull() { + assertThat(converter.convertToDatabaseColumn(null)).isNull(); + } + + @Test + public void convertToDatabaseColumn_convertsCorrectly() { + DateTime dateTime = DateTime.parse("2019-09-01T01:01:01"); + assertThat(converter.convertToDatabaseColumn(dateTime).getTime()) + .isEqualTo(dateTime.getMillis()); + } + + @Test + public void convertToEntityAttribute_returnsNullIfInputIsNull() { + assertThat(converter.convertToEntityAttribute(null)).isNull(); + } + + @Test + public void convertToEntityAttribute_convertsCorrectly() { + DateTime dateTime = DateTime.parse("2019-09-01T01:01:01Z"); + long millis = dateTime.getMillis(); + assertThat(converter.convertToEntityAttribute(new Timestamp(millis))).isEqualTo(dateTime); + } + + static DateTime parseDateTime(String value) { + return ISODateTimeFormat.dateTimeNoMillis().withOffsetParsed().parseDateTime(value); + } + + @Test + public void converter_generatesTimestampWithNormalizedZone() { + DateTime dt = parseDateTime("2019-09-01T01:01:01Z"); + TestEntity entity = new TestEntity("normalized_utc_time", dt); + jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity)); + TestEntity retrievedEntity = + jpaTm() + .transact( + () -> jpaTm().getEntityManager().find(TestEntity.class, "normalized_utc_time")); + assertThat(retrievedEntity.dt.toString()).isEqualTo("2019-09-01T01:01:01.000Z"); + } + + @Test + public void converter_convertsNonUtcZoneCorrectly() { + DateTime dt = parseDateTime("2019-09-01T01:01:01-05:00"); + TestEntity entity = new TestEntity("new_york_time", dt); + + jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity)); + TestEntity retrievedEntity = + jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "new_york_time")); + assertThat(retrievedEntity.dt.toString()).isEqualTo("2019-09-01T06:01:01.000Z"); + } + + @Entity(name = "TestEntity") // Override entity name to avoid the nested class reference. + private static class TestEntity extends ImmutableObject { + + @Id String name; + + DateTime dt; + + public TestEntity() {} + + TestEntity(String name, DateTime dt) { + this.name = name; + this.dt = dt; + } + } +} diff --git a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java index 004f58eba..9fdc87fa4 100644 --- a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java +++ b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java @@ -21,6 +21,7 @@ import google.registry.model.transaction.JpaTransactionManagerRuleTest; import google.registry.persistence.BloomFilterConverterTest; import google.registry.persistence.CreateAutoTimestampConverterTest; import google.registry.persistence.CurrencyUnitConverterTest; +import google.registry.persistence.DateTimeConverterTest; import google.registry.persistence.JodaMoneyConverterTest; import google.registry.persistence.UpdateAutoTimestampConverterTest; import google.registry.persistence.ZonedDateTimeConverterTest; @@ -46,6 +47,7 @@ import org.junit.runners.Suite.SuiteClasses; ClaimsListDaoTest.class, CreateAutoTimestampConverterTest.class, CurrencyUnitConverterTest.class, + DateTimeConverterTest.class, JodaMoneyConverterTest.class, JpaTransactionManagerImplTest.class, JpaTransactionManagerRuleTest.class,