aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorvlad <vlad@drivergrp.com>2016-07-15 19:41:26 -0400
committervlad <vlad@drivergrp.com>2016-07-15 19:41:26 -0400
commitc0d574dc6134e4f406875ea5a1301ba46602a6ec (patch)
tree606a56d184bd8c4d67f98b5aa3fafa3640a8190f /src
downloaddriver-core-c0d574dc6134e4f406875ea5a1301ba46602a6ec.tar.gz
driver-core-c0d574dc6134e4f406875ea5a1301ba46602a6ec.tar.bz2
driver-core-c0d574dc6134e4f406875ea5a1301ba46602a6ec.zip
Initial commit with standard lib, might be used a example of cake
Diffstat (limited to 'src')
-rw-r--r--src/main/resources/logback.xml22
-rw-r--r--src/main/scala/com/drivergrp/core/DriverApp.scala75
-rw-r--r--src/main/scala/com/drivergrp/core/Service.scala34
-rw-r--r--src/main/scala/com/drivergrp/core/Swagger.scala45
-rw-r--r--src/main/scala/com/drivergrp/core/config.scala43
-rw-r--r--src/main/scala/com/drivergrp/core/database.scala45
-rw-r--r--src/main/scala/com/drivergrp/core/execution.scala28
-rw-r--r--src/main/scala/com/drivergrp/core/generators.scala53
-rw-r--r--src/main/scala/com/drivergrp/core/id.scala24
-rw-r--r--src/main/scala/com/drivergrp/core/logging.scala132
-rw-r--r--src/main/scala/com/drivergrp/core/messages.scala74
-rw-r--r--src/main/scala/com/drivergrp/core/package.scala19
-rw-r--r--src/main/scala/com/drivergrp/core/rest.scala92
-rw-r--r--src/main/scala/com/drivergrp/core/stats.scala50
-rw-r--r--src/main/scala/com/drivergrp/core/time.scala79
15 files changed, 815 insertions, 0 deletions
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
new file mode 100644
index 0000000..1b96003
--- /dev/null
+++ b/src/main/resources/logback.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+ <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+ <target>System.out</target>
+ <encoder>
+ <pattern>%date{MM/dd HH:mm:ss} %-5level[%.15thread] %logger{1} - %msg%n</pattern>
+ </encoder>
+ </appender>
+ <logger name="slick.backend.DatabaseComponent.actio" level="debug"/>
+ <logger name="slick.jdbc" level="error" />
+ <logger name="slick.ast" level="error" />
+ <logger name="slick.memory" level="error" />
+ <logger name="slick.relational" level="error" />
+ <logger name="slick.compiler" level="error" />
+ <logger name="com.wordnik" level="error" />
+ <logger name="com.github" level="error" />
+ <root level="debug">
+ <appender-ref ref="CONSOLE"/>
+ </root>
+
+</configuration>
diff --git a/src/main/scala/com/drivergrp/core/DriverApp.scala b/src/main/scala/com/drivergrp/core/DriverApp.scala
new file mode 100644
index 0000000..01fee03
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/DriverApp.scala
@@ -0,0 +1,75 @@
+package com.drivergrp.core
+
+import akka.actor.ActorSystem
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.RouteResult._
+import akka.stream.ActorMaterializer
+import com.drivergrp.core.config.ConfigModule
+import com.drivergrp.core.logging.LoggerModule
+
+
+trait DriverApp {
+ this: ConfigModule with LoggerModule =>
+
+ def interface: String = "localhost"
+ def port: Int = 8080
+
+ def services: Seq[Service]
+
+
+ val servicesInstances = services
+ activateServices(servicesInstances)
+ scheduleServicesDeactivation(servicesInstances)
+
+ implicit val actorSystem = ActorSystem("spray-routing", config)
+ implicit val executionContext = actorSystem.dispatcher
+ implicit val materializer = ActorMaterializer()(actorSystem)
+
+ val serviceTypes = servicesInstances.flatMap(_.serviceTypes)
+ val swaggerService = new Swagger(actorSystem, serviceTypes, config)
+ val swaggerRoutes = swaggerService.routes ~ swaggerService.swaggerUI
+
+ Http()(actorSystem).bindAndHandle(
+ route2HandlerFlow(logRequestResult("log")(servicesInstances.map(_.route).foldLeft(swaggerRoutes) { _ ~ _ })),
+ interface, port)(materializer)
+
+
+ /**
+ * Initializes services
+ */
+ protected def activateServices(servicesInstances: Seq[Service]) = {
+ servicesInstances.foreach { service =>
+ Console.print(s"Service ${service.name} starts ...")
+ try {
+ service.activate()
+ } catch {
+ case t: Throwable =>
+ log.fatal(s"Service ${service.name} failed to activate", t)
+ Console.print(" Failed! (check log)")
+ }
+ Console.println(" Done")
+ }
+ }
+
+ /**
+ * Schedules services to be deactivated on the app shutdown
+ */
+ protected def scheduleServicesDeactivation(servicesInstances: Seq[Service]) = {
+ Runtime.getRuntime.addShutdownHook(new Thread() {
+ override def run(): Unit = {
+ servicesInstances.foreach { service =>
+ Console.print(s"Service ${service.name} shutting down ...")
+ try {
+ service.deactivate()
+ } catch {
+ case t: Throwable =>
+ log.fatal(s"Service ${service.name} failed to deactivate", t)
+ Console.print(" Failed! (check log)")
+ }
+ Console.println(" Done")
+ }
+ }
+ })
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/Service.scala b/src/main/scala/com/drivergrp/core/Service.scala
new file mode 100644
index 0000000..4404c9c
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/Service.scala
@@ -0,0 +1,34 @@
+package com.drivergrp.core
+
+import akka.http.scaladsl.server.{Route, RouteConcatenation}
+
+
+trait Service {
+ import scala.reflect.runtime.universe._
+
+ val name: String
+ def route: Route
+ def serviceTypes: Seq[Type]
+
+ def activate(): Unit = {}
+ def deactivate(): Unit = {}
+}
+
+/**
+ * Service implementation which may be used to composed a few
+ *
+ * @param name more general name of the composite service,
+ * must be provided as there is no good way to automatically
+ * generalize the name from the composed services' names
+ * @param services services to compose into a single one
+ */
+class CompositeService(val name: String, services: Seq[Service])
+ extends Service with RouteConcatenation {
+
+ def route: Route = services.map(_.route).reduce(_ ~ _)
+
+ def serviceTypes = services.flatMap(_.serviceTypes)
+
+ override def activate() = services.foreach(_.activate())
+ override def deactivate() = services.reverse.foreach(_.deactivate())
+} \ No newline at end of file
diff --git a/src/main/scala/com/drivergrp/core/Swagger.scala b/src/main/scala/com/drivergrp/core/Swagger.scala
new file mode 100644
index 0000000..428920c
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/Swagger.scala
@@ -0,0 +1,45 @@
+package com.drivergrp.core
+
+import akka.actor.ActorSystem
+import akka.stream.ActorMaterializer
+import com.github.swagger.akka.model._
+import com.github.swagger.akka.{HasActorSystem, SwaggerHttpService}
+import com.typesafe.config.Config
+
+import scala.reflect.runtime.universe._
+
+
+class Swagger(override val actorSystem: ActorSystem,
+ override val apiTypes: Seq[Type],
+ val config: Config) extends SwaggerHttpService with HasActorSystem {
+
+ val materializer = ActorMaterializer()(actorSystem)
+
+ override val host = "localhost:8080" //the url of your api, not swagger's json endpoint
+ override val basePath = config.getString("swagger.basePath")
+ override val apiDocsPath = config.getString("swagger.docsPath")
+
+ override val info = Info(
+ config.getString("swagger.apiInfo.description"),
+ config.getString("swagger.apiVersion"),
+ config.getString("swagger.apiInfo.title"),
+ config.getString("swagger.apiInfo.termsOfServiceUrl"),
+ contact = Some(Contact(
+ config.getString("swagger.apiInfo.contact.name"),
+ config.getString("swagger.apiInfo.contact.url"),
+ config.getString("swagger.apiInfo.contact.email")
+ )),
+ license = Some(License(
+ config.getString("swagger.apiInfo.license"),
+ config.getString("swagger.apiInfo.licenseUrl")
+ )),
+ vendorExtensions = Map())
+
+ def swaggerUI = get {
+ pathPrefix("") {
+ pathEndOrSingleSlash {
+ getFromResource("swagger-ui/index.html")
+ }
+ } ~ getFromResourceDirectory("swagger-ui")
+ }
+} \ No newline at end of file
diff --git a/src/main/scala/com/drivergrp/core/config.scala b/src/main/scala/com/drivergrp/core/config.scala
new file mode 100644
index 0000000..5a89752
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/config.scala
@@ -0,0 +1,43 @@
+package com.drivergrp.core
+
+import java.io.File
+import com.typesafe.config.{Config, ConfigFactory}
+
+
+object config {
+
+ trait ConfigModule {
+ def config: Config
+ }
+
+ /**
+ * Configuration implementation providing config which is specified as the parameter
+ * which might be used for testing purposes
+ *
+ * @param config fixed config to provide
+ */
+ class DefaultConfigModule(val config: Config) extends ConfigModule
+
+ /**
+ * Configuration implementation reading default typesafe config
+ */
+ trait TypesafeConfigModule extends ConfigModule {
+
+ private val internalConfig: Config = {
+ val configDefaults =
+ ConfigFactory.load(this.getClass.getClassLoader, "application.conf")
+
+ scala.sys.props.get("application.config") match {
+
+ case Some(filename) =>
+ ConfigFactory.parseFile(new File(filename)).withFallback(configDefaults)
+
+ case None => configDefaults
+ }
+ }
+
+ protected val rootConfig = internalConfig
+
+ val config = rootConfig
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/database.scala b/src/main/scala/com/drivergrp/core/database.scala
new file mode 100644
index 0000000..5eb9d28
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/database.scala
@@ -0,0 +1,45 @@
+package com.drivergrp.core
+
+import com.drivergrp.core.id.{Id, Name}
+
+import scala.concurrent.Future
+
+
+object database {
+
+ import slick.backend.DatabaseConfig
+ import slick.driver.JdbcProfile
+
+
+ trait DatabaseModule {
+ val profile: JdbcProfile
+ val database: JdbcProfile#Backend#Database
+ }
+
+ trait ConfigDatabaseModule extends DatabaseModule {
+
+ protected def databaseConfigKey: String
+
+ private val dbConfig: DatabaseConfig[JdbcProfile] = DatabaseConfig.forConfig(databaseConfigKey)
+
+ val profile: JdbcProfile = dbConfig.driver
+ val database: JdbcProfile#Backend#Database = dbConfig.db
+ }
+
+ trait DatabaseObject {
+ def createTables(): Future[Unit]
+ def disconnect(): Unit
+ }
+
+ trait IdColumnTypes {
+ this: DatabaseModule =>
+
+ import profile.api._
+
+ implicit def idColumnType[T] =
+ MappedColumnType.base[Id[T], Long]({ id => id: Long }, { id => Id[T](id) })
+
+ implicit def nameColumnType[T] =
+ MappedColumnType.base[Name[T], String]({ name => name: String }, { name => Name[T](name) })
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/execution.scala b/src/main/scala/com/drivergrp/core/execution.scala
new file mode 100644
index 0000000..7274f00
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/execution.scala
@@ -0,0 +1,28 @@
+package com.drivergrp.core
+
+
+object execution {
+
+ import scala.concurrent.ExecutionContext
+ import java.util.concurrent.Executors
+ import akka.actor.ActorSystem
+
+
+ trait ExecutionContextModule {
+
+ def executionContext: ExecutionContext
+ }
+
+ trait FixedThreadsExecutionContext extends ExecutionContextModule {
+
+ def threadsNumber: Int
+
+ val executionContext: ExecutionContext =
+ ExecutionContext.fromExecutor(Executors.newFixedThreadPool(threadsNumber))
+ }
+
+ trait ActorSystemModule {
+
+ def actorSystem: ActorSystem
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/generators.scala b/src/main/scala/com/drivergrp/core/generators.scala
new file mode 100644
index 0000000..89d6e56
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/generators.scala
@@ -0,0 +1,53 @@
+package com.drivergrp.core
+
+import java.math.MathContext
+
+import com.drivergrp.core.id.{Id, Name}
+import com.drivergrp.core.time.{Time, TimeRange}
+
+import scala.reflect.ClassTag
+import scala.util.Random
+
+
+object generators {
+
+ private val random = new Random
+ import random._
+
+ private val DefaultMaxLength = 100
+
+
+ def nextId[T](): Id[T] = Id[T](nextLong())
+
+ def nextName[T](maxLength: Int = DefaultMaxLength): Name[T] = Name[T](nextString(maxLength))
+
+ def nextString(maxLength: Int = DefaultMaxLength) = random.nextString(maxLength)
+
+ def nextOption[T](value: => T): Option[T] = if(nextBoolean) Option(value) else None
+
+ def nextPair[L, R](left: => L, right: => R): (L, R) = (left, right)
+
+ 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 nextTimeRange(): TimeRange = TimeRange(nextTime(), nextTime())
+
+ def nextBigDecimal(multiplier: Double = 1000000.00, precision: Int = 2): BigDecimal =
+ BigDecimal(multiplier * nextDouble, new MathContext(precision))
+
+ def oneOf[T](items: Seq[T]): T = items(nextInt(items.size))
+
+ def arrayOf[T : ClassTag](generator: => T, maxLength: Int = DefaultMaxLength): Array[T] = Array.fill(maxLength)(generator)
+
+ def seqOf[T](generator: => T, maxLength: Int = DefaultMaxLength): Seq[T] = Seq.fill(maxLength)(generator)
+
+ def vectorOf[T](generator: => T, maxLength: Int = DefaultMaxLength): Vector[T] = Vector.fill(maxLength)(generator)
+
+ def listOf[T](generator: => T, maxLength: Int = DefaultMaxLength): List[T] = List.fill(maxLength)(generator)
+
+ def setOf[T](generator: => T, maxLength: Int = DefaultMaxLength): Set[T] = seqOf(generator, maxLength).toSet
+
+ def mapOf[K, V](maxLength: Int, keyGenerator: => K, valueGenerator: => V): Map[K, V] =
+ seqOf(nextPair(keyGenerator, valueGenerator), maxLength).toMap
+} \ No newline at end of file
diff --git a/src/main/scala/com/drivergrp/core/id.scala b/src/main/scala/com/drivergrp/core/id.scala
new file mode 100644
index 0000000..29b8d99
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/id.scala
@@ -0,0 +1,24 @@
+package com.drivergrp.core
+
+import scalaz._
+
+
+object id {
+
+ trait Tagged[+V, +Tag]
+ type @@[+V, +Tag] = V with Tagged[V, Tag]
+
+ type Id[+Tag] = Long @@ Tag
+ object Id {
+ def apply[Tag](value: Long) = value.asInstanceOf[Id[Tag]]
+ }
+
+ implicit def idEqual[T]: Equal[Id[T]] = Equal.equal[Id[T]](_ == _)
+
+ type Name[+Tag] = String @@ Tag
+ object Name {
+ def apply[Tag](value: String) = value.asInstanceOf[Name[Tag]]
+ }
+
+ implicit def nameEqual[T]: Equal[Name[T]] = Equal.equal[Name[T]](_ == _)
+}
diff --git a/src/main/scala/com/drivergrp/core/logging.scala b/src/main/scala/com/drivergrp/core/logging.scala
new file mode 100644
index 0000000..35f3a1b
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/logging.scala
@@ -0,0 +1,132 @@
+package com.drivergrp.core
+
+import com.typesafe.scalalogging.{LazyLogging, StrictLogging}
+import org.slf4j.Marker
+
+
+object logging {
+
+ trait LoggerModule {
+
+ def log: Logger
+ }
+
+ trait NopLoggerModule extends LoggerModule {
+
+ def log: Logger = new NopLogger()
+ }
+
+ /**
+ * Defines `logger` as a lazy value initialized with an underlying `org.slf4j.Logger`
+ * named according to the class into which this trait is mixed.
+ */
+ trait LazyLoggerModule extends LoggerModule {
+ this: LazyLogging =>
+
+ override val log: Logger = new TypesafeScalaLogger(logger)
+ }
+
+ /**
+ * Defines `logger` as a value initialized with an underlying `org.slf4j.Logger`
+ * named according to the class into which this trait is mixed.
+ */
+ trait StrictLoggerModule extends LoggerModule {
+ this: StrictLogging =>
+
+ override val log: Logger = new TypesafeScalaLogger(logger)
+ }
+
+
+ trait Logger {
+
+ def fatal(message: String): Unit
+ def fatal(message: String, cause: Throwable): Unit
+ def fatal(message: String, args: AnyRef*): Unit
+ def fatal(marker: Marker, message: String): Unit
+ def fatal(marker: Marker, message: String, cause: Throwable): Unit
+ def fatal(marker: Marker, message: String, args: AnyRef*): Unit
+
+ def error(message: String): Unit
+ def error(message: String, cause: Throwable): Unit
+ def error(message: String, args: AnyRef*): Unit
+ def error(marker: Marker, message: String): Unit
+ def error(marker: Marker, message: String, cause: Throwable): Unit
+ def error(marker: Marker, message: String, args: AnyRef*): Unit
+
+ def audit(message: String): Unit
+ def audit(message: String, cause: Throwable): Unit
+ def audit(message: String, args: AnyRef*): Unit
+ def audit(marker: Marker, message: String): Unit
+ def audit(marker: Marker, message: String, cause: Throwable): Unit
+ def audit(marker: Marker, message: String, args: AnyRef*): Unit
+
+ def debug(message: String): Unit
+ def debug(message: String, cause: Throwable): Unit
+ def debug(message: String, args: AnyRef*): Unit
+ def debug(marker: Marker, message: String): Unit
+ def debug(marker: Marker, message: String, cause: Throwable): Unit
+ def debug(marker: Marker, message: String, args: AnyRef*): Unit
+ }
+
+ class TypesafeScalaLogger(scalaLogging: com.typesafe.scalalogging.Logger) extends Logger {
+
+ def fatal(message: String): Unit = scalaLogging.error(message)
+ def fatal(message: String, cause: Throwable): Unit = scalaLogging.error(message, cause)
+ def fatal(message: String, args: AnyRef*): Unit = scalaLogging.error(message, args)
+ def fatal(marker: Marker, message: String): Unit = scalaLogging.error(marker, message)
+ def fatal(marker: Marker, message: String, cause: Throwable): Unit = scalaLogging.error(marker, message, cause)
+ def fatal(marker: Marker, message: String, args: AnyRef*): Unit = scalaLogging.error(marker, message, args)
+
+ def error(message: String): Unit = scalaLogging.warn(message)
+ def error(message: String, cause: Throwable): Unit = scalaLogging.warn(message, cause)
+ def error(message: String, args: AnyRef*): Unit = scalaLogging.warn(message, args)
+ def error(marker: Marker, message: String): Unit = scalaLogging.warn(marker, message)
+ def error(marker: Marker, message: String, cause: Throwable): Unit = scalaLogging.warn(marker, message, cause)
+ def error(marker: Marker, message: String, args: AnyRef*): Unit = scalaLogging.warn(marker, message, args)
+
+ def audit(message: String): Unit = scalaLogging.info(message)
+ def audit(message: String, cause: Throwable): Unit = scalaLogging.info(message, cause)
+ def audit(message: String, args: AnyRef*): Unit = scalaLogging.info(message, args)
+ def audit(marker: Marker, message: String): Unit = scalaLogging.info(marker, message)
+ def audit(marker: Marker, message: String, cause: Throwable): Unit = scalaLogging.info(marker, message, cause)
+ def audit(marker: Marker, message: String, args: AnyRef*): Unit = scalaLogging.info(marker, message, args)
+
+ def debug(message: String): Unit = scalaLogging.debug(message)
+ def debug(message: String, cause: Throwable): Unit = scalaLogging.debug(message, cause)
+ def debug(message: String, args: AnyRef*): Unit = scalaLogging.debug(message, args)
+ def debug(marker: Marker, message: String): Unit = scalaLogging.debug(marker, message)
+ def debug(marker: Marker, message: String, cause: Throwable): Unit = scalaLogging.debug(marker, message, cause)
+ def debug(marker: Marker, message: String, args: AnyRef*): Unit = scalaLogging.debug(marker, message, args)
+ }
+
+ class NopLogger() extends Logger {
+
+ def fatal(message: String): Unit = {}
+ def fatal(message: String, cause: Throwable): Unit = {}
+ def fatal(message: String, args: AnyRef*): Unit = {}
+ def fatal(marker: Marker, message: String): Unit = {}
+ def fatal(marker: Marker, message: String, cause: Throwable): Unit = {}
+ def fatal(marker: Marker, message: String, args: AnyRef*): Unit = {}
+
+ def error(message: String): Unit = {}
+ def error(message: String, cause: Throwable): Unit = {}
+ def error(message: String, args: AnyRef*): Unit = {}
+ def error(marker: Marker, message: String): Unit = {}
+ def error(marker: Marker, message: String, cause: Throwable): Unit = {}
+ def error(marker: Marker, message: String, args: AnyRef*): Unit = {}
+
+ def audit(message: String): Unit = {}
+ def audit(message: String, cause: Throwable): Unit = {}
+ def audit(message: String, args: AnyRef*): Unit = {}
+ def audit(marker: Marker, message: String): Unit = {}
+ def audit(marker: Marker, message: String, cause: Throwable): Unit = {}
+ def audit(marker: Marker, message: String, args: AnyRef*): Unit = {}
+
+ def debug(message: String): Unit = {}
+ def debug(message: String, cause: Throwable): Unit = {}
+ def debug(message: String, args: AnyRef*): Unit = {}
+ def debug(marker: Marker, message: String): Unit = {}
+ def debug(marker: Marker, message: String, cause: Throwable): Unit = {}
+ def debug(marker: Marker, message: String, args: AnyRef*): Unit = {}
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/messages.scala b/src/main/scala/com/drivergrp/core/messages.scala
new file mode 100644
index 0000000..cf0bb6c
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/messages.scala
@@ -0,0 +1,74 @@
+package com.drivergrp.core
+
+
+import java.util.Locale
+
+import com.drivergrp.core.config.ConfigModule
+import com.drivergrp.core.logging.{Logger, LoggerModule}
+
+import scala.collection.JavaConverters._
+import scala.collection.concurrent.TrieMap
+
+/**
+ * Scala internationalization (i18n) support
+ */
+object messages {
+
+ trait MessagesModule {
+
+ def messages: Messages
+ }
+
+ trait ConfigMessagesModule extends MessagesModule {
+ this: ConfigModule with LoggerModule =>
+
+ private val loadedFromConfig = new TrieMap[Locale, Messages]()
+ val locale: Locale = Locale.US
+
+ val messages: Messages = {
+ loadedFromConfig.getOrElseUpdate(locale, {
+ val map = config.getConfig(locale.getISO3Language).root().unwrapped().asScala.mapValues(_.toString).toMap
+ Messages(map, locale, log)
+ })
+ }
+ }
+
+
+ case class Messages(map: Map[String, String], locale: Locale, log: Logger) {
+
+ /**
+ * Returns message for the key
+ *
+ * @param key key
+ * @return message
+ */
+ def apply(key: String): String = {
+ map.get(key) match {
+ case Some(message) => message
+ case None =>
+ log.error(s"Message with key $key not found for locale " + locale.getDisplayName)
+ key
+ }
+ }
+
+ /**
+ * Returns message for the key and formats that with parameters
+ *
+ * @example "Hello {0}!" with "Joe" will be "Hello Joe!"
+ *
+ * @param key key
+ * @param params params to be embedded
+ * @return formatted message
+ */
+ def apply(key: String, params: Any*): String = {
+
+ def format(formatString: String, params: Seq[Any]) =
+ params.zipWithIndex.foldLeft(formatString) {
+ case (res, (value, index)) => res.replaceAll(s"{$index}", value.toString)
+ }
+
+ val template = apply(key)
+ format(template, params)
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/scala/com/drivergrp/core/package.scala b/src/main/scala/com/drivergrp/core/package.scala
new file mode 100644
index 0000000..3c19431
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/package.scala
@@ -0,0 +1,19 @@
+package com.drivergrp
+
+
+package object core {
+ import scala.language.reflectiveCalls
+
+ def make[T](v: => T)(f: T => Unit): T = {
+ val value = v; f(value); value
+ }
+
+ def using[R <: { def close() }, P](r: => R)(f: R => P): P = {
+ val resource = r
+ try {
+ f(resource)
+ } finally {
+ resource.close()
+ }
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/rest.scala b/src/main/scala/com/drivergrp/core/rest.scala
new file mode 100644
index 0000000..e121640
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/rest.scala
@@ -0,0 +1,92 @@
+package com.drivergrp.core
+
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.model.{HttpRequest, HttpResponse}
+import akka.http.scaladsl.server.{Directive, _}
+import akka.http.scaladsl.util.FastFuture._
+import akka.stream.ActorMaterializer
+import akka.util.Timeout
+import com.drivergrp.core.execution.{ActorSystemModule, ExecutionContextModule}
+import com.drivergrp.core.id.{Id, Name}
+import com.drivergrp.core.logging.LoggerModule
+import com.drivergrp.core.stats.StatsModule
+import com.drivergrp.core.time.TimeRange
+import com.drivergrp.core.time.provider.TimeModule
+import spray.json.{DeserializationException, JsNumber, JsString, JsValue, RootJsonFormat}
+
+import scala.concurrent.Future
+import scala.concurrent.duration._
+import scala.language.postfixOps
+import scala.util.{Failure, Success, Try}
+import scalaz.{Failure => _, Success => _, _}
+
+
+object rest {
+
+ trait RestService {
+ this: ActorSystemModule with LoggerModule with StatsModule with TimeModule with ExecutionContextModule =>
+
+ protected implicit val timeout = Timeout(5 seconds)
+
+ implicit val materializer = ActorMaterializer()(actorSystem)
+
+
+ def sendRequest(request: HttpRequest): Future[HttpResponse] = {
+
+ log.audit(s"Sending to ${request.uri} request $request")
+
+ val requestTime = time.currentTime()
+ val response = Http()(actorSystem).singleRequest(request)(materializer)
+
+ response.onComplete {
+ case Success(_) =>
+ val responseTime = time.currentTime()
+ log.audit(s"Response from ${request.uri} to request $request is successful")
+ stats.recordStats(Seq("request", request.uri.toString, "success"), TimeRange(requestTime, responseTime), 1)
+
+ case Failure(t) =>
+ val responseTime = time.currentTime()
+ log.audit(s"Failed to receive response from ${request.uri} to request $request")
+ log.error(s"Failed to receive response from ${request.uri} to request $request", t)
+ stats.recordStats(Seq("request", request.uri.toString, "fail"), TimeRange(requestTime, responseTime), 1)
+ } (executionContext)
+
+ response
+ }
+ }
+
+
+ object basicFormats {
+
+ implicit def idFormat[T] = new RootJsonFormat[Id[T]] {
+ def write(id: Id[T]) = JsNumber(id)
+
+ def read(value: JsValue) = value match {
+ case JsNumber(id) => Id[T](id.toLong)
+ case _ => throw new DeserializationException("Id expects number")
+ }
+ }
+
+ implicit def nameFormat[T] = new RootJsonFormat[Name[T]] {
+ def write(name: Name[T]) = JsNumber(name)
+
+ def read(value: JsValue): Name[T] = value match {
+ case JsString(name) => Name[T](name)
+ case _ => throw new DeserializationException("Name expects string")
+ }
+ }
+ }
+
+ trait OptionTDirectives {
+
+ /**
+ * "Unwraps" a `OptionT[Future, T]` and runs the inner route after future
+ * completion with the future's value as an extraction of type `Try[T]`.
+ */
+ def onComplete[T](optionT: OptionT[Future, T]): Directive1[Try[Option[T]]] =
+ Directive { inner ⇒ ctx ⇒
+ import ctx.executionContext
+ optionT.run.fast.transformWith(t ⇒ inner(Tuple1(t))(ctx))
+ }
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/stats.scala b/src/main/scala/com/drivergrp/core/stats.scala
new file mode 100644
index 0000000..2a173df
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/stats.scala
@@ -0,0 +1,50 @@
+package com.drivergrp.core
+
+import com.drivergrp.core.logging.LoggerModule
+import com.drivergrp.core.time.{Time, TimeRange}
+
+object stats {
+
+ type StatsKey = String
+ type StatsKeys = Seq[StatsKey]
+
+
+ trait StatsModule {
+
+ def stats: Stats
+ }
+
+ trait Stats {
+
+ def recordStats(keys: StatsKeys, interval: TimeRange, value: BigDecimal): Unit
+
+ def recordStats(keys: StatsKeys, interval: TimeRange, value: Int): Unit =
+ recordStats(keys, interval, BigDecimal(value))
+
+ def recordStats(key: StatsKey, interval: TimeRange, value: BigDecimal): Unit =
+ recordStats(Vector(key), interval, value)
+
+ def recordStats(key: StatsKey, interval: TimeRange, value: Int): Unit =
+ recordStats(Vector(key), interval, BigDecimal(value))
+
+ def recordStats(keys: StatsKeys, time: Time, value: BigDecimal): Unit =
+ recordStats(keys, TimeRange(time, time), value)
+
+ def recordStats(keys: StatsKeys, time: Time, value: Int): Unit =
+ recordStats(keys, TimeRange(time, time), BigDecimal(value))
+
+ def recordStats(key: StatsKey, time: Time, value: BigDecimal): Unit =
+ recordStats(Vector(key), TimeRange(time, time), value)
+
+ def recordStats(key: StatsKey, time: Time, value: Int): Unit =
+ recordStats(Vector(key), TimeRange(time, time), BigDecimal(value))
+ }
+
+ trait LogStats extends Stats {
+ this: LoggerModule =>
+
+ def recordStats(keys: StatsKeys, interval: TimeRange, value: BigDecimal): Unit = {
+ log.audit(s"${keys.mkString(".")}(${interval.start.millis}-${interval.end.millis})=${value.toString}")
+ }
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/time.scala b/src/main/scala/com/drivergrp/core/time.scala
new file mode 100644
index 0000000..645c991
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/time.scala
@@ -0,0 +1,79 @@
+package com.drivergrp.core
+
+import java.text.SimpleDateFormat
+import java.util.{Calendar, Date, GregorianCalendar}
+
+import scala.concurrent.duration.Duration
+
+object time {
+
+ // The most useful time units
+ val Second = 1000L
+ val Seconds = Second
+ val Minute = 60 * Seconds
+ val Minutes = Minute
+ val Hour = 60 * Minutes
+ val Hours = Hour
+ val Day = 24 * Hours
+ val Days = Day
+ val Week = 7 * Days
+ val Weeks = Week
+
+
+ case class Time(millis: Long) extends AnyVal {
+
+ def isBefore(anotherTime: Time): Boolean = millis < anotherTime.millis
+
+ def isAfter(anotherTime: Time): Boolean = millis > anotherTime.millis
+
+ def advanceBy(duration: Duration): Time = Time(millis + duration.length)
+ }
+
+ case class TimeRange(start: Time, end: Time)
+
+ implicit def timeOrdering: Ordering[Time] = Ordering.by(_.millis)
+
+
+ def startOfMonth(time: Time) = {
+ make(new GregorianCalendar()) { cal =>
+ cal.setTime(new Date(time.millis))
+ cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH))
+ Time(cal.getTime.getTime)
+ }
+ }
+
+ def textualDate(time: Time): String =
+ new SimpleDateFormat("MMMM d, yyyy").format(new Date(time.millis))
+
+ def textualTime(time: Time): String =
+ new SimpleDateFormat("MMM dd, yyyy hh:mm:ss a").format(new Date(time.millis))
+
+
+ object provider {
+
+ /**
+ * Time providers are supplying code with current times
+ * and are extremely useful for testing to check how system is going
+ * to behave at specific moments in time.
+ *
+ * All the calls to receive current time must be made using time
+ * provider injected to the caller.
+ */
+
+ trait TimeModule {
+ def time: TimeProvider
+ }
+
+ trait TimeProvider {
+ def currentTime(): Time
+ }
+
+ final class SystemTimeProvider extends TimeProvider {
+ def currentTime() = Time(System.currentTimeMillis())
+ }
+
+ final class SpecificTimeProvider(time: Time) extends TimeProvider {
+ def currentTime() = time
+ }
+ }
+}