-
Notifications
You must be signed in to change notification settings - Fork 566
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Mappers for database date/time/timestamps. (#1485)
* Mappers for database date/time/timestamps. Signed-off-by: Tomas Kraus <Tomas.Kraus@oracle.com>
- Loading branch information
1 parent
2ca4b74
commit 41220ce
Showing
4 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
129 changes: 129 additions & 0 deletions
129
dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientMapperProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
* Copyright (c) 2020 Oracle and/or its affiliates. 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 io.helidon.dbclient.common; | ||
|
||
import java.sql.Timestamp; | ||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.time.LocalTime; | ||
import java.time.ZoneOffset; | ||
import java.time.ZonedDateTime; | ||
import java.util.Calendar; | ||
import java.util.Collections; | ||
import java.util.Date; | ||
import java.util.GregorianCalendar; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
import io.helidon.common.mapper.Mapper; | ||
import io.helidon.common.mapper.spi.MapperProvider; | ||
|
||
/** | ||
* Java Service loader service to get database types mappers. | ||
*/ | ||
public class DbClientMapperProvider implements MapperProvider { | ||
|
||
/** | ||
* Mappers index {@code [Class<SOURCE>, Class<TARGET>] -> Mapper<SOURCE, TARGET>}. | ||
*/ | ||
private static final Map<Class<?>, Map<Class<?>, Mapper<?, ?>>> MAPPERS = initMappers(); | ||
|
||
private static Map<Class<?>, Map<Class<?>, Mapper<?, ?>>> initMappers() { | ||
// All mappers index | ||
Map<Class<?>, Map<Class<?>, Mapper<?, ?>>> mappers = new HashMap<>(3); | ||
// * Mappers for java.sql.Timestamp source | ||
Map<Class<?>, Mapper<Timestamp, Object>> sqlTimestampMap = new HashMap<>(4); | ||
// - Mapper for java.sql.Timestamp to java.util.Date | ||
sqlTimestampMap.put( | ||
Date.class, | ||
source -> source); | ||
// - Mapper for java.sql.Timestamp to java.time.LocalDateTime | ||
sqlTimestampMap.put( | ||
LocalDateTime.class, | ||
(Timestamp source) -> source != null | ||
? source.toLocalDateTime() | ||
: null); | ||
// - Mapper for java.sql.Timestamp to java.time.ZonedDateTime | ||
sqlTimestampMap.put( | ||
ZonedDateTime.class, | ||
(Timestamp source) -> source != null | ||
? ZonedDateTime.ofInstant(source.toInstant(), ZoneOffset.UTC) | ||
: null); | ||
// - Mapper for java.sql.Timestamp to java.util.Calendar | ||
sqlTimestampMap.put( | ||
Calendar.class, | ||
DbClientMapperProvider::sqlTimestampToGregorianCalendar); | ||
// - Mapper for java.sql.Timestamp to java.util.GregorianCalendar | ||
sqlTimestampMap.put( | ||
GregorianCalendar.class, | ||
DbClientMapperProvider::sqlTimestampToGregorianCalendar); | ||
mappers.put( | ||
Timestamp.class, | ||
Collections.unmodifiableMap(sqlTimestampMap)); | ||
// * Mappers for java.sql.Date source | ||
Map<Class<?>, Mapper<java.sql.Date, Object>> sqlDateMap = new HashMap<>(2); | ||
// - Mapper for java.sql.Date to java.util.Date | ||
sqlDateMap.put( | ||
Date.class, | ||
source -> source); | ||
// - Mapper for java.sql.Date to java.time.LocalDate | ||
sqlDateMap.put( | ||
LocalDate.class, | ||
(java.sql.Date source) -> source != null | ||
? source.toLocalDate() | ||
: null); | ||
mappers.put( | ||
java.sql.Date.class, | ||
Collections.unmodifiableMap(sqlDateMap)); | ||
// * Mappers for java.sql.Time source | ||
Map<Class<?>, Mapper<java.sql.Time, Object>> sqlTimeMap = new HashMap<>(2); | ||
// - Mapper for java.sql.Time to java.util.Date | ||
sqlTimeMap.put( | ||
Date.class, | ||
source -> source); | ||
// - Mapper for java.sql.Time to java.time.LocalTime | ||
sqlTimeMap.put( | ||
LocalTime.class, | ||
(java.sql.Time source) -> source != null | ||
? source.toLocalTime() | ||
: null); | ||
mappers.put( | ||
java.sql.Time.class, | ||
Collections.unmodifiableMap(sqlTimeMap)); | ||
return Collections.unmodifiableMap(mappers); | ||
} | ||
|
||
@Override | ||
public <SOURCE, TARGET> Optional<Mapper<?, ?>> mapper(Class<SOURCE> sourceClass, Class<TARGET> targetClass) { | ||
Map<Class<?>, Mapper<?, ?>> targetMap = MAPPERS.get(sourceClass); | ||
if (targetMap == null) { | ||
return Optional.empty(); | ||
} | ||
Mapper<?, ?> mapper = targetMap.get(targetClass); | ||
return mapper == null ? Optional.empty() : Optional.of(mapper); | ||
} | ||
|
||
/** | ||
* Maps {@link java.sql.Timestamp} to {@link java.util.Calendar} with zone set to UTC. | ||
*/ | ||
private static GregorianCalendar sqlTimestampToGregorianCalendar(Timestamp source) { | ||
return source != null | ||
? GregorianCalendar.from(ZonedDateTime.ofInstant(source.toInstant(), ZoneOffset.UTC)) | ||
: null; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
...t/common/src/main/resources/META-INF/services/io.helidon.common.mapper.spi.MapperProvider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# | ||
# Copyright (c) 2020 Oracle and/or its affiliates. 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. | ||
# | ||
|
||
io.helidon.dbclient.common.DbClientMapperProvider |
144 changes: 144 additions & 0 deletions
144
dbclient/common/src/test/java/io/helidon/dbclient/common/mapper/MapperTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
* Copyright (c) 2020 Oracle and/or its affiliates. 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 io.helidon.dbclient.common.mapper; | ||
|
||
import java.sql.Timestamp; | ||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.time.LocalTime; | ||
import java.time.OffsetDateTime; | ||
import java.time.ZoneOffset; | ||
import java.time.ZonedDateTime; | ||
import java.time.temporal.ChronoField; | ||
import java.util.Calendar; | ||
import java.util.Date; | ||
import java.util.GregorianCalendar; | ||
|
||
import io.helidon.common.mapper.MapperManager; | ||
|
||
import org.junit.jupiter.api.BeforeAll; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.hamcrest.CoreMatchers.*; | ||
import static org.hamcrest.MatcherAssert.assertThat; | ||
|
||
/** | ||
* Test database types {@link Mapper}s. | ||
*/ | ||
public class MapperTest { | ||
|
||
private static MapperManager mm; | ||
|
||
@BeforeAll | ||
public static void init() { | ||
mm = MapperManager.create(); | ||
} | ||
|
||
/** | ||
* Current date with time set to 00:00:00. | ||
* | ||
* @return current date with time set to 00:00:00 | ||
*/ | ||
private static OffsetDateTime currentDate() { | ||
return OffsetDateTime.now() | ||
.with(ChronoField.HOUR_OF_DAY, 0) | ||
.with(ChronoField.MINUTE_OF_HOUR, 0) | ||
.with(ChronoField.SECOND_OF_MINUTE, 0) | ||
.with(ChronoField.NANO_OF_SECOND, 0); | ||
} | ||
|
||
/** | ||
* Current time with date set to 1. 1. 1970 (epoch). | ||
* | ||
* @return current time with date set to 1. 1. 1970 (epoch) | ||
*/ | ||
private static OffsetDateTime currentTime() { | ||
return OffsetDateTime.now() | ||
.with(ChronoField.YEAR, 1970) | ||
.with(ChronoField.MONTH_OF_YEAR, 1) | ||
.with(ChronoField.DAY_OF_MONTH, 1); | ||
} | ||
|
||
@Test | ||
public void testSqlDateToLocalDate() { | ||
OffsetDateTime dt = currentDate(); | ||
java.sql.Date source = new java.sql.Date(dt.toInstant().toEpochMilli()); | ||
LocalDate target = mm.map(source, java.sql.Date.class, LocalDate.class); | ||
assertThat(target.toEpochSecond(LocalTime.MIN, dt.getOffset()), is(source.getTime()/1000)); | ||
} | ||
|
||
@Test | ||
public void testSqlDateToUtilDate() { | ||
OffsetDateTime dt = OffsetDateTime.now(); | ||
java.sql.Date source = new java.sql.Date(dt.toInstant().toEpochMilli()); | ||
Date target = mm.map(source, java.sql.Date.class, Date.class); | ||
assertThat(target.getTime(), is(source.getTime())); | ||
} | ||
|
||
@Test | ||
public void testSqlTimeToLocalTime() { | ||
OffsetDateTime dt = currentTime(); | ||
java.sql.Time source = new java.sql.Time(dt.toInstant().toEpochMilli()); | ||
LocalTime target = mm.map(source, java.sql.Time.class, LocalTime.class); | ||
assertThat(target.toEpochSecond(LocalDate.EPOCH, dt.getOffset()), is(source.getTime()/1000)); | ||
} | ||
|
||
@Test | ||
public void testSqlTimeToUtilDate() { | ||
OffsetDateTime dt = OffsetDateTime.now(); | ||
java.sql.Time source = new java.sql.Time(dt.toInstant().toEpochMilli()); | ||
Date target = mm.map(source, java.sql.Time.class, Date.class); | ||
assertThat(target.getTime(), is(source.getTime())); | ||
} | ||
|
||
@Test | ||
public void testSqlTimestampToGregorianCalendar() { | ||
Timestamp source = new Timestamp(System.currentTimeMillis()); | ||
GregorianCalendar target = mm.map(source, Timestamp.class, GregorianCalendar.class); | ||
assertThat(target.getTimeInMillis(), is(source.getTime())); | ||
} | ||
|
||
@Test | ||
public void testSqlTimestampToCalendar() { | ||
Timestamp source = new Timestamp(System.currentTimeMillis()); | ||
Calendar target = mm.map(source, Timestamp.class, Calendar.class); | ||
assertThat(target.getTimeInMillis(), is(source.getTime())); | ||
} | ||
|
||
@Test | ||
public void testSqlTimestampToLocalDateTime() { | ||
// Need to know time zone too. | ||
OffsetDateTime dt = OffsetDateTime.now(); | ||
Timestamp source = new Timestamp(dt.toInstant().toEpochMilli()); | ||
LocalDateTime target = mm.map(source, Timestamp.class, LocalDateTime.class); | ||
assertThat(target.atOffset(dt.getOffset()).toInstant().toEpochMilli(), is(source.getTime())); | ||
} | ||
|
||
@Test | ||
public void testSqlTimestampToUtilDate() { | ||
Timestamp source = new Timestamp(System.currentTimeMillis()); | ||
Date target = mm.map(source, Timestamp.class, Date.class); | ||
assertThat(target.getTime(), is(source.getTime())); | ||
} | ||
|
||
@Test | ||
public void testSqlTimestampToZonedDateTime() { | ||
Timestamp source = new Timestamp(System.currentTimeMillis()); | ||
ZonedDateTime target = mm.map(source, Timestamp.class, ZonedDateTime.class); | ||
assertThat(target.toInstant().toEpochMilli(), is(source.getTime())); | ||
} | ||
|
||
} |