aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorSergey Nastich <nastich@users.noreply.github.com>2018-08-24 13:41:27 -0400
committerGitHub <noreply@github.com>2018-08-24 13:41:27 -0400
commitdf1a2f7fcbdd85ac84162cf8eae8cdb6bb25cbb5 (patch)
treecdfb528e1d4569bfc973b784e6a763b02108442c /src/main
parent46306f0c8f7e88e55a3b18df8ab212e9ea5e01f1 (diff)
downloaddriver-core-df1a2f7fcbdd85ac84162cf8eae8cdb6bb25cbb5.tar.gz
driver-core-df1a2f7fcbdd85ac84162cf8eae8cdb6bb25cbb5.tar.bz2
driver-core-df1a2f7fcbdd85ac84162cf8eae8cdb6bb25cbb5.zip
Migration to `java.time.Instant` and `java.time.LocalDate`: Part 1 (#200)v1.13.0
* Add semi-backwards-compatible JSON formats and path matchers for java.time.Instant and java.time.LocalDate * Use `Clock` in `ApplicationContext` instead of `TimeProvider`, deprecate `TimeProvider` * Add `ChangeableClock` in time package for tests * Add generators for instants and LocalDates
Diffstat (limited to 'src/main')
-rw-r--r--src/main/scala/xyz/driver/core/app/init.scala26
-rw-r--r--src/main/scala/xyz/driver/core/generators.scala7
-rw-r--r--src/main/scala/xyz/driver/core/json.scala57
-rw-r--r--src/main/scala/xyz/driver/core/logging/MdcExecutionContext.scala20
-rw-r--r--src/main/scala/xyz/driver/core/time.scala45
5 files changed, 117 insertions, 38 deletions
diff --git a/src/main/scala/xyz/driver/core/app/init.scala b/src/main/scala/xyz/driver/core/app/init.scala
index 119c91a..f1e80b9 100644
--- a/src/main/scala/xyz/driver/core/app/init.scala
+++ b/src/main/scala/xyz/driver/core/app/init.scala
@@ -1,6 +1,7 @@
package xyz.driver.core.app
import java.nio.file.{Files, Paths}
+import java.time.Clock
import java.util.concurrent.{Executor, Executors}
import akka.actor.ActorSystem
@@ -9,7 +10,7 @@ import com.typesafe.config.{Config, ConfigFactory}
import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory
import xyz.driver.core.logging.MdcExecutionContext
-import xyz.driver.core.time.provider.{SystemTimeProvider, TimeProvider}
+import xyz.driver.core.time.provider.TimeProvider
import xyz.driver.tracing.{GoogleTracer, NoTracer, Tracer}
import scala.concurrent.ExecutionContext
@@ -23,7 +24,9 @@ object init {
val gitHeadCommit: scala.Option[String]
}
- case class ApplicationContext(config: Config, time: TimeProvider, log: Logger)
+ case class ApplicationContext(config: Config, clock: Clock, log: Logger) {
+ val time: TimeProvider = clock
+ }
/** NOTE: This needs to be the first that is run when application starts.
* Otherwise if another command causes the logger to be instantiated,
@@ -68,9 +71,9 @@ object init {
val actorSystem =
ActorSystem(s"$serviceName-actors", Option(config), Option.empty[ClassLoader], Option(executionContext))
- Runtime.getRuntime.addShutdownHook(new Thread() {
- override def run(): Unit = Try(actorSystem.terminate())
- })
+ sys.addShutdownHook {
+ Try(actorSystem.terminate())
+ }
actorSystem
}
@@ -81,14 +84,11 @@ object init {
def newFixedMdcExecutionContext(capacity: Int): MdcExecutionContext =
toMdcExecutionContext(Executors.newFixedThreadPool(capacity))
- def defaultApplicationContext(): ApplicationContext = {
- val config = getEnvironmentSpecificConfig()
-
- val time = new SystemTimeProvider()
- val log = Logger(LoggerFactory.getLogger(classOf[DriverApp]))
-
- ApplicationContext(config, time, log)
- }
+ def defaultApplicationContext(): ApplicationContext =
+ ApplicationContext(
+ config = getEnvironmentSpecificConfig(),
+ clock = Clock.systemUTC(),
+ log = Logger(LoggerFactory.getLogger(classOf[DriverApp])))
def createDefaultApplication(
modules: Seq[Module],
diff --git a/src/main/scala/xyz/driver/core/generators.scala b/src/main/scala/xyz/driver/core/generators.scala
index d57980e..d00b6dd 100644
--- a/src/main/scala/xyz/driver/core/generators.scala
+++ b/src/main/scala/xyz/driver/core/generators.scala
@@ -2,6 +2,7 @@ package xyz.driver.core
import enumeratum._
import java.math.MathContext
+import java.time.{Instant, LocalDate, ZoneOffset}
import java.util.UUID
import xyz.driver.core.time.{Time, TimeOfDay, TimeRange}
@@ -88,7 +89,9 @@ object generators {
def nextTriad[F, S, T](first: => F, second: => S, third: => T): (F, S, T) = (first, second, third)
- def nextTime(): Time = Time(math.abs(nextLong() % System.currentTimeMillis))
+ def nextInstant(): Instant = Instant.ofEpochMilli(math.abs(nextLong() % System.currentTimeMillis))
+
+ def nextTime(): Time = nextInstant()
def nextTimeOfDay: TimeOfDay = TimeOfDay(java.time.LocalTime.MIN.plusSeconds(nextLong), java.util.TimeZone.getDefault)
@@ -103,6 +106,8 @@ object generators {
def nextDate(): Date = nextTime().toDate(java.util.TimeZone.getTimeZone("UTC"))
+ def nextLocalDate(): LocalDate = nextInstant().atZone(ZoneOffset.UTC).toLocalDate
+
def nextDayOfWeek(): DayOfWeek = oneOf(DayOfWeek.All)
def nextBigDecimal(multiplier: Double = 1000000.00, precision: Int = 2): BigDecimal =
diff --git a/src/main/scala/xyz/driver/core/json.scala b/src/main/scala/xyz/driver/core/json.scala
index 639af22..98725fb 100644
--- a/src/main/scala/xyz/driver/core/json.scala
+++ b/src/main/scala/xyz/driver/core/json.scala
@@ -1,6 +1,8 @@
package xyz.driver.core
import java.net.InetAddress
+import java.time.format.DateTimeFormatter
+import java.time.{Instant, LocalDate}
import java.util.{TimeZone, UUID}
import akka.http.scaladsl.marshalling.{Marshaller, Marshalling}
@@ -23,6 +25,7 @@ import xyz.driver.core.time.{Time, TimeOfDay}
import scala.reflect.{ClassTag, classTag}
import scala.reflect.runtime.universe._
import scala.util.Try
+import scala.util.control.NonFatal
object json {
import DefaultJsonProtocol._
@@ -77,25 +80,49 @@ object json {
}
}
- def TimeInPath: PathMatcher1[Time] =
+ def TimeInPath: PathMatcher1[Time] = InstantInPath.map(instant => Time(instant.toEpochMilli))
+
+ private def timestampInPath: PathMatcher1[Long] =
PathMatcher("""[+-]?\d*""".r) flatMap { string =>
- try Some(Time(string.toLong))
+ try Some(string.toLong)
catch { case _: IllegalArgumentException => None }
}
- implicit val timeFormat = new RootJsonFormat[Time] {
+ def InstantInPath: PathMatcher1[Instant] =
+ new PathMatcher1[Instant] {
+ def apply(path: Path): PathMatcher.Matching[Tuple1[Instant]] = path match {
+ case Path.Segment(head, tail) =>
+ try Matched(tail, Tuple1(Instant.parse(head)))
+ catch {
+ case NonFatal(_) => Unmatched
+ }
+ case _ => Unmatched
+ }
+ } | timestampInPath.map(Instant.ofEpochMilli)
+
+ implicit val timeFormat: RootJsonFormat[Time] = new RootJsonFormat[Time] {
def write(time: Time) = JsObject("timestamp" -> JsNumber(time.millis))
- def read(value: JsValue): Time = value match {
+ def read(value: JsValue): Time = Time(instantFormat.read(value))
+ }
+
+ implicit val instantFormat: JsonFormat[Instant] = new JsonFormat[Instant] {
+ def write(instant: Instant): JsValue = JsString(instant.toString)
+
+ def read(value: JsValue): Instant = value match {
case JsObject(fields) =>
fields
.get("timestamp")
.flatMap {
- case JsNumber(millis) => Some(Time(millis.toLong))
+ case JsNumber(millis) => Some(Instant.ofEpochMilli(millis.longValue()))
case _ => None
}
- .getOrElse(throw DeserializationException("Time expects number"))
- case _ => throw DeserializationException("Time expects number")
+ .getOrElse(deserializationError(s"Instant expects ISO timestamp but got ${value.compactPrint}"))
+ case JsNumber(millis) => Instant.ofEpochMilli(millis.longValue())
+ case JsString(str) =>
+ try Instant.parse(str)
+ catch { case NonFatal(_) => deserializationError(s"Instant expects ISO timestamp but got $str") }
+ case _ => deserializationError(s"Instant expects ISO timestamp but got ${value.compactPrint}")
}
}
@@ -140,6 +167,22 @@ object json {
}
}
+ implicit val localDateFormat = new RootJsonFormat[LocalDate] {
+ val format = DateTimeFormatter.ISO_LOCAL_DATE
+
+ def write(date: LocalDate): JsValue = JsString(date.format(format))
+ def read(value: JsValue): LocalDate = value match {
+ case JsString(dateString) =>
+ try LocalDate.parse(dateString, format)
+ catch {
+ case NonFatal(_) =>
+ throw deserializationError(s"Malformed ISO 8601 Date. Expected YYYY-MM-DD, but got $dateString.")
+ }
+ case _ =>
+ throw deserializationError(s"Malformed ISO 8601 Date. Expected YYYY-MM-DD, but got ${value.compactPrint}.")
+ }
+ }
+
implicit val monthFormat = new RootJsonFormat[Month] {
def write(month: Month) = JsNumber(month)
def read(value: JsValue): Month = value match {
diff --git a/src/main/scala/xyz/driver/core/logging/MdcExecutionContext.scala b/src/main/scala/xyz/driver/core/logging/MdcExecutionContext.scala
index df21b48..11c99f9 100644
--- a/src/main/scala/xyz/driver/core/logging/MdcExecutionContext.scala
+++ b/src/main/scala/xyz/driver/core/logging/MdcExecutionContext.scala
@@ -13,18 +13,16 @@ import scala.concurrent.ExecutionContext
class MdcExecutionContext(executionContext: ExecutionContext) extends ExecutionContext {
override def execute(runnable: Runnable): Unit = {
val callerMdc = MDC.getCopyOfContextMap
- executionContext.execute(new Runnable {
- def run(): Unit = {
- // copy caller thread diagnostic context to execution thread
- Option(callerMdc).foreach(MDC.setContextMap)
- try {
- runnable.run()
- } finally {
- // the thread might be reused, so we clean up for the next use
- MDC.clear()
- }
+ executionContext.execute { () =>
+ // copy caller thread diagnostic context to execution thread
+ Option(callerMdc).foreach(MDC.setContextMap)
+ try {
+ runnable.run()
+ } finally {
+ // the thread might be reused, so we clean up for the next use
+ MDC.clear()
}
- })
+ }
}
override def reportFailure(cause: Throwable): Unit = executionContext.reportFailure(cause)
diff --git a/src/main/scala/xyz/driver/core/time.scala b/src/main/scala/xyz/driver/core/time.scala
index 6dbd173..c7a32ad 100644
--- a/src/main/scala/xyz/driver/core/time.scala
+++ b/src/main/scala/xyz/driver/core/time.scala
@@ -1,6 +1,7 @@
package xyz.driver.core
import java.text.SimpleDateFormat
+import java.time.{Clock, Instant, ZoneId, ZoneOffset}
import java.util._
import java.util.concurrent.TimeUnit
@@ -40,6 +41,14 @@ object time {
cal.setTimeInMillis(millis)
date.Date(cal.get(Calendar.YEAR), date.Month(cal.get(Calendar.MONTH)), cal.get(Calendar.DAY_OF_MONTH))
}
+
+ def toInstant: Instant = Instant.ofEpochMilli(millis)
+ }
+
+ object Time {
+ implicit def timeOrdering: Ordering[Time] = Ordering.by(_.millis)
+
+ implicit def apply(instant: Instant): Time = Time(instant.toEpochMilli)
}
/**
@@ -127,11 +136,6 @@ object time {
}
}
- object Time {
-
- implicit def timeOrdering: Ordering[Time] = Ordering.by(_.millis)
- }
-
final case class TimeRange(start: Time, end: Time) {
def duration: Duration = FiniteDuration(end.millis - start.millis, MILLISECONDS)
}
@@ -149,6 +153,18 @@ object time {
def textualTime(timezone: TimeZone)(time: Time): String =
make(new SimpleDateFormat("MMM dd, yyyy hh:mm:ss a"))(_.setTimeZone(timezone)).format(new Date(time.millis))
+ class ChangeableClock(@volatile var instant: Instant, val zone: ZoneId = ZoneOffset.UTC) extends Clock {
+
+ def tick(duration: FiniteDuration): Unit =
+ instant = instant.plusNanos(duration.toNanos)
+
+ val getZone: ZoneId = zone
+
+ def withZone(zone: ZoneId): Clock = new ChangeableClock(instant, zone = zone)
+
+ override def toString: String = "ChangeableClock(" + instant + "," + zone + ")"
+ }
+
object provider {
/**
@@ -159,17 +175,34 @@ object time {
* All the calls to receive current time must be made using time
* provider injected to the caller.
*/
+ @deprecated(
+ "Use java.time.Clock instead. Note that xyz.driver.core.Time and xyz.driver.core.date.Date will also be deprecated soon!",
+ "0.13.0")
trait TimeProvider {
def currentTime(): Time
+ def toClock: Clock
+ }
+
+ final implicit class ClockTimeProvider(clock: Clock) extends TimeProvider {
+ def currentTime(): Time = Time(clock.instant().toEpochMilli)
+
+ val toClock: Clock = clock
}
final class SystemTimeProvider extends TimeProvider {
def currentTime() = Time(System.currentTimeMillis())
+
+ lazy val toClock: Clock = Clock.systemUTC()
}
+
final val SystemTimeProvider = new SystemTimeProvider
final class SpecificTimeProvider(time: Time) extends TimeProvider {
- def currentTime() = time
+
+ def currentTime(): Time = time
+
+ lazy val toClock: Clock = Clock.fixed(time.toInstant, ZoneOffset.UTC)
}
+
}
}