diff options
author | Kseniya Tomskikh <ktomskih@datamonsters.co> | 2017-10-30 16:29:46 +0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-30 16:29:46 +0700 |
commit | 604fbf0a7a082bc440c0778abd6f90005b210c16 (patch) | |
tree | fd846d42448b2d3f432056898f0c3433475ea0da /src/main/scala/xyz/driver/pdsuicommon | |
parent | 8811d60442d060097027b33784ece9a704458e1d (diff) | |
parent | d92a2795a02b9711aa9aed92a770f85377b31460 (diff) | |
download | rest-query-0.11.0.tar.gz rest-query-0.11.0.tar.bz2 rest-query-0.11.0.zip |
Merge pull request #54 from drivergroup/PDSUI-2336v0.11.0
PDSUI-2336 Cleanup
Diffstat (limited to 'src/main/scala/xyz/driver/pdsuicommon')
26 files changed, 74 insertions, 1178 deletions
diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala deleted file mode 100644 index 3bf9192..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala +++ /dev/null @@ -1,140 +0,0 @@ -package xyz.driver.pdsuicommon.concurrent - -import java.time.LocalDateTime -import java.time.temporal.ChronoUnit - -import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueue.Item -import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueueRepositoryAdapter.Strategy -import xyz.driver.pdsuicommon.db._ -import xyz.driver.pdsuicommon.db.repositories.BridgeUploadQueueRepository -import xyz.driver.pdsuicommon.logging._ - -import scala.concurrent.duration.{Duration, FiniteDuration} -import scala.concurrent.{ExecutionContext, Future} -import scala.util.Try - -object BridgeUploadQueueRepositoryAdapter { - - /** - * Defines how we work with queue, when an user attempts to remove/tryRetry an item. - */ - sealed trait Strategy { - - def onComplete: Strategy.OnComplete - - def on(attempt: Int): Strategy.OnAttempt - - } - - object Strategy { - - /** - * Works forever, but has a limit for intervals. - */ - final case class LimitExponential(startInterval: FiniteDuration, - intervalFactor: Double, - maxInterval: FiniteDuration, - onComplete: OnComplete) - extends Strategy { - - override def on(attempt: Int): OnAttempt = { - OnAttempt.Continue(intervalFor(attempt).min(maxInterval)) - } - - private def intervalFor(attempt: Int): Duration = { - Try(startInterval * Math.pow(intervalFactor, attempt.toDouble)) - .getOrElse(maxInterval) - } - } - - /** - * Used only in tests. - */ - final case class Stop(onComplete: OnComplete = OnComplete.Delete) extends Strategy { - - override def on(attempt: Int) = OnAttempt.Complete - - } - - /** - * Used only in tests. - */ - final case class Constant(interval: FiniteDuration) extends Strategy { - - override val onComplete = OnComplete.Delete - - override def on(attempt: Int) = OnAttempt.Continue(interval) - - } - - sealed trait OnComplete - object OnComplete { - case object Delete extends OnComplete - case object Mark extends OnComplete - - implicit def toPhiString(x: OnAttempt): PhiString = Unsafe(x.toString) - } - - sealed trait OnAttempt - object OnAttempt { - case object Complete extends OnAttempt - final case class Continue(interval: Duration) extends OnAttempt - - implicit def toPhiString(x: OnAttempt): PhiString = Unsafe(x.toString) - } - } -} - -class BridgeUploadQueueRepositoryAdapter(strategy: Strategy, repository: BridgeUploadQueueRepository, dbIo: DbIo)( - implicit executionContext: ExecutionContext) - extends BridgeUploadQueue with PhiLogging { - - override def add(item: Item): Future[Item] = dbIo.runAsync(repository.add(item)) - - override def get(kind: String): Future[Option[Item]] = dbIo.runAsync(repository.getOne(kind)) - - override def complete(kind: String, tag: String): Future[Unit] = { - import Strategy.OnComplete._ - - strategy.onComplete match { - case Delete => dbIo.runAsync(repository.delete(kind, tag)) - case Mark => - dbIo.runAsyncTx { - repository.getById(kind, tag) match { - case Some(x) => repository.update(x.copy(completed = true)) - case None => throw new RuntimeException(s"Can not find the task: kind=$kind, tag=$tag") - } - } - } - } - - /** - * Tries to continue the task or complete it - */ - override def tryRetry(item: Item): Future[Option[Item]] = { - import Strategy.OnAttempt._ - - logger.trace(phi"tryRetry($item)") - - val newAttempts = item.attempts + 1 - val action = strategy.on(newAttempts) - logger.debug(phi"Action for ${Unsafe(newAttempts)}: $action") - - action match { - case Continue(newInterval) => - val draftItem = item.copy( - attempts = newAttempts, - nextAttempt = LocalDateTime.now().plus(newInterval.toMillis, ChronoUnit.MILLIS) - ) - - logger.debug(draftItem) - dbIo.runAsync { - Some(repository.update(draftItem)) - } - - case Complete => - logger.warn(phi"All attempts are out for $item, complete the task") - complete(item.kind, item.tag).map(_ => None) - } - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/DbCommand.scala b/src/main/scala/xyz/driver/pdsuicommon/db/DbCommand.scala deleted file mode 100644 index 0af104e..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/db/DbCommand.scala +++ /dev/null @@ -1,15 +0,0 @@ -package xyz.driver.pdsuicommon.db - -import scala.concurrent.Future - -trait DbCommand { - def runSync(): Unit - def runAsync(transactions: DbIo): Future[Unit] -} - -object DbCommand { - val Empty: DbCommand = new DbCommand { - override def runSync(): Unit = {} - override def runAsync(transactions: DbIo): Future[Unit] = Future.successful(()) - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/DbCommandFactory.scala b/src/main/scala/xyz/driver/pdsuicommon/db/DbCommandFactory.scala deleted file mode 100644 index 1e2fb74..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/db/DbCommandFactory.scala +++ /dev/null @@ -1,14 +0,0 @@ -package xyz.driver.pdsuicommon.db - -import scala.concurrent.{ExecutionContext, Future} - -trait DbCommandFactory[T] { - def createCommand(orig: T)(implicit ec: ExecutionContext): Future[DbCommand] -} - -object DbCommandFactory { - def empty[T]: DbCommandFactory[T] = new DbCommandFactory[T] { - override def createCommand(orig: T)(implicit ec: ExecutionContext): Future[DbCommand] = - Future.successful(DbCommand.Empty) - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/EntityExtractorDerivation.scala b/src/main/scala/xyz/driver/pdsuicommon/db/EntityExtractorDerivation.scala deleted file mode 100644 index 02ba322..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/db/EntityExtractorDerivation.scala +++ /dev/null @@ -1,66 +0,0 @@ -package xyz.driver.pdsuicommon.db - -import java.sql.ResultSet - -import io.getquill.NamingStrategy -import io.getquill.dsl.EncodingDsl - -import scala.language.experimental.macros -import scala.reflect.macros.blackbox - -trait EntityExtractorDerivation[Naming <: NamingStrategy] { this: EncodingDsl => - - /* - Simple Quill extractor derivation for [[T]] - Only case classes available. Type parameters is not supported - */ - def entityExtractor[T]: (ResultSet => T) = macro EntityExtractorDerivation.impl[T] -} - -object EntityExtractorDerivation { - def impl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = { - import c.universe._ - val namingStrategy = c.prefix.actualType - .baseType(c.weakTypeOf[EntityExtractorDerivation[NamingStrategy]].typeSymbol) - .typeArgs - .head - .typeSymbol - .companion - val functionBody = { - val tpe = weakTypeOf[T] - val resultOpt = tpe.decls.collectFirst { - // Find first constructor of T - case cons: MethodSymbol if cons.isConstructor => - // Create param list for constructor - val params = cons.paramLists.flatten.map { param => - val t = param.typeSignature - val paramName = param.name.toString - val col = q"$namingStrategy.column($paramName)" - // Resolve implicit decoders (from SqlContext) and apply ResultSet for each - val d = q"implicitly[${c.prefix}.Decoder[$t]]" - // Minus 1 cause Quill JDBC decoders make plus one. - // ¯\_(ツ)_/¯ - val i = q"row.findColumn($col) - 1" - val decoderName = TermName(paramName + "Decoder") - val valueName = TermName(paramName + "Value") - ( - q"val $decoderName = $d", - q"val $valueName = $decoderName($i, row)", - valueName - ) - } - // Call constructor with param list - q""" - ..${params.map(_._1)} - ..${params.map(_._2)} - new $tpe(..${params.map(_._3)}) - """ - } - resultOpt match { - case Some(result) => result - case None => c.abort(c.enclosingPosition, s"Can not derive extractor for $tpe. Constructor not found.") - } - } - q"(row: java.sql.ResultSet) => $functionBody" - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala b/src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala deleted file mode 100644 index ac42a34..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala +++ /dev/null @@ -1,9 +0,0 @@ -package xyz.driver.pdsuicommon.db - -import scala.concurrent.Future - -object FakeDbIo extends DbIo { - override def runAsync[T](f: => T): Future[T] = Future.successful(f) - override def runAsyncTx[T](f: => T): Future[T] = Future.successful(f) - override def runSyncTx[T](f: => T): Unit = f -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala b/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala deleted file mode 100644 index 9d2664d..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala +++ /dev/null @@ -1,90 +0,0 @@ -package xyz.driver.pdsuicommon.db - -import java.io.Closeable -import java.time._ -import java.util.concurrent.Executors -import javax.sql.DataSource - -import com.typesafe.config.Config -import io.getquill._ -import xyz.driver.pdsuicommon.concurrent.MdcExecutionContext -import xyz.driver.pdsuicommon.db.MySqlContext.Settings -import xyz.driver.pdsuicommon.error.IncorrectIdException -import xyz.driver.pdsuicommon.logging._ - -import scala.concurrent.ExecutionContext -import scala.util.control.NonFatal -import scala.util.{Failure, Success, Try} - -object MySqlContext extends PhiLogging { - - final case class DbCredentials(user: String, - password: String, - host: String, - port: Int, - dbName: String, - dbCreateFlag: Boolean, - dbContext: String, - connectionParams: String, - url: String) - - final case class Settings(credentials: DbCredentials, connection: Config, threadPoolSize: Int) - - def apply(settings: Settings): MySqlContext = { - // Prevent leaking credentials to a log - Try(JdbcContextConfig(settings.connection).dataSource) match { - case Success(dataSource) => new MySqlContext(dataSource, settings) - case Failure(NonFatal(e)) => - logger.error(phi"Can not load dataSource, error: ${Unsafe(e.getClass.getName)}") - throw new IllegalArgumentException("Can not load dataSource from config. Check your database and config") - } - } -} - -class MySqlContext(dataSource: DataSource with Closeable, settings: Settings) - extends MysqlJdbcContext[MysqlEscape](dataSource) with TransactionalContext - with EntityExtractorDerivation[Literal] { - - private val tpe = Executors.newFixedThreadPool(settings.threadPoolSize) - - implicit val executionContext: ExecutionContext = { - val orig = ExecutionContext.fromExecutor(tpe) - MdcExecutionContext.from(orig) - } - - override def close(): Unit = { - super.close() - tpe.shutdownNow() - } - - /** - * Overrode, because Quill JDBC optionDecoder pass null inside decoders. - * If custom decoder don't have special null handler, it will failed. - * - * @see https://github.com/getquill/quill/issues/535 - */ - implicit override def optionDecoder[T](implicit d: Decoder[T]): Decoder[Option[T]] = - decoder( - sqlType = d.sqlType, - row => - index => { - try { - val res = d(index - 1, row) - if (row.wasNull) { - None - } else { - Some(res) - } - } catch { - case _: NullPointerException => None - case _: IncorrectIdException => None - } - } - ) - - final implicit class LocalDateTimeDbOps(val left: LocalDateTime) { - - // scalastyle:off - def <=(right: LocalDateTime): Quoted[Boolean] = quote(infix"$left <= $right".as[Boolean]) - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/MysqlQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/MysqlQueryBuilder.scala deleted file mode 100644 index e2936e3..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/db/MysqlQueryBuilder.scala +++ /dev/null @@ -1,88 +0,0 @@ -package xyz.driver.pdsuicommon.db - -import java.sql.ResultSet - -import xyz.driver.pdsuicommon.logging._ -import io.getquill.{MySQLDialect, MysqlEscape} - -import scala.collection.breakOut - -object MysqlQueryBuilder extends PhiLogging { - import xyz.driver.pdsuicommon.db.QueryBuilder._ - - def apply[T](tableName: String, - lastUpdateFieldName: Option[String], - nullableFields: Set[String], - links: Set[TableLink], - runner: Runner[T], - countRunner: CountRunner): MysqlQueryBuilder[T] = { - val parameters = MysqlQueryBuilderParameters( - tableData = TableData(tableName, lastUpdateFieldName, nullableFields), - links = links.map(x => x.foreignTableName -> x)(breakOut) - ) - new MysqlQueryBuilder[T](parameters)(runner, countRunner) - } - - def apply[T](tableName: String, - lastUpdateFieldName: Option[String], - nullableFields: Set[String], - links: Set[TableLink], - extractor: (ResultSet) => T)(implicit sqlContext: MySqlContext): MysqlQueryBuilder[T] = { - - val runner: Runner[T] = { parameters => - val (sql, binder) = parameters.toSql(namingStrategy = MysqlEscape) - logger.trace(phi"Query for execute: ${Unsafe(sql)}") - sqlContext.executeQuery[T](sql, binder, { resultSet => - extractor(resultSet) - }) - } - - val countRunner: CountRunner = { parameters => - val (sql, binder) = parameters.toSql(countQuery = true, namingStrategy = MysqlEscape) - logger.trace(phi"Query for execute: ${Unsafe(sql)}") - sqlContext - .executeQuery[CountResult]( - sql, - binder, { resultSet => - val count = resultSet.getInt(1) - val lastUpdate = if (parameters.tableData.lastUpdateFieldName.isDefined) { - Option(sqlContext.localDateTimeDecoder.decoder(2, resultSet)) - } else None - - (count, lastUpdate) - } - ) - .head - } - - apply[T]( - tableName = tableName, - lastUpdateFieldName = lastUpdateFieldName, - nullableFields = nullableFields, - links = links, - runner = runner, - countRunner = countRunner - ) - } -} - -class MysqlQueryBuilder[T](parameters: MysqlQueryBuilderParameters)(implicit runner: QueryBuilder.Runner[T], - countRunner: QueryBuilder.CountRunner) - extends QueryBuilder[T, MySQLDialect, MysqlEscape](parameters) { - - def withFilter(newFilter: SearchFilterExpr): QueryBuilder[T, MySQLDialect, MysqlEscape] = { - new MysqlQueryBuilder[T](parameters.copy(filter = newFilter)) - } - - def withSorting(newSorting: Sorting): QueryBuilder[T, MySQLDialect, MysqlEscape] = { - new MysqlQueryBuilder[T](parameters.copy(sorting = newSorting)) - } - - def withPagination(newPagination: Pagination): QueryBuilder[T, MySQLDialect, MysqlEscape] = { - new MysqlQueryBuilder[T](parameters.copy(pagination = Some(newPagination))) - } - - def resetPagination: QueryBuilder[T, MySQLDialect, MysqlEscape] = { - new MysqlQueryBuilder[T](parameters.copy(pagination = None)) - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala b/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala index 7bdfd1b..0098a64 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala @@ -1,7 +1,7 @@ package xyz.driver.pdsuicommon.db import java.io.Closeable -import java.time._ +import java.time.{LocalDateTime, ZoneOffset} import java.util.UUID import java.util.concurrent.Executors import javax.sql.DataSource @@ -35,8 +35,7 @@ object PostgresContext extends PhiLogging { } class PostgresContext(val dataSource: DataSource with Closeable, settings: Settings) - extends PostgresJdbcContext[SnakeCase](dataSource) with TransactionalContext - with EntityExtractorDerivation[SnakeCase] { + extends PostgresJdbcContext[SnakeCase](dataSource) with TransactionalContext { private val tpe = Executors.newFixedThreadPool(settings.threadPoolSize) diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/PostgresQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/PostgresQueryBuilder.scala deleted file mode 100644 index 0ddf811..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/db/PostgresQueryBuilder.scala +++ /dev/null @@ -1,108 +0,0 @@ -package xyz.driver.pdsuicommon.db - -import java.sql.ResultSet - -import io.getquill.{PostgresDialect, PostgresEscape} -import xyz.driver.pdsuicommon.db.PostgresQueryBuilder.SmartPostgresEscape - -import scala.collection.breakOut - -object PostgresQueryBuilder { - - import xyz.driver.pdsuicommon.db.QueryBuilder._ - - trait SmartPostgresEscape extends PostgresEscape { - override def column(s: String): String = - if (s.startsWith("$")) s else super.column(s) - override def default(s: String): String = - s.split("\\.").map(ss => s""""$ss"""").mkString(".") - } - - object SmartPostgresEscape extends SmartPostgresEscape - - type Escape = SmartPostgresEscape - val Escape = SmartPostgresEscape - - def apply[T](tableName: String, - lastUpdateFieldName: Option[String], - nullableFields: Set[String], - links: Set[TableLink], - runner: Runner[T], - countRunner: CountRunner): PostgresQueryBuilder[T] = { - val parameters = PostgresQueryBuilderParameters( - tableData = TableData(tableName, lastUpdateFieldName, nullableFields), - links = links.map(x => x.foreignTableName -> x)(breakOut) - ) - new PostgresQueryBuilder[T](parameters)(runner, countRunner) - } - - def apply[T](tableName: String, - lastUpdateFieldName: Option[String], - nullableFields: Set[String], - links: Set[TableLink], - extractor: ResultSet => T)(implicit sqlContext: PostgresContext): PostgresQueryBuilder[T] = { - apply(tableName, QueryBuilderParameters.AllFields, lastUpdateFieldName, nullableFields, links, extractor) - } - - def apply[T](tableName: String, - fields: Set[String], - lastUpdateFieldName: Option[String], - nullableFields: Set[String], - links: Set[TableLink], - extractor: ResultSet => T)(implicit sqlContext: PostgresContext): PostgresQueryBuilder[T] = { - - val runner: Runner[T] = { parameters => - val (sql, binder) = parameters.toSql(countQuery = false, fields = fields, namingStrategy = SmartPostgresEscape) - sqlContext.executeQuery[T](sql, binder, { resultSet => - extractor(resultSet) - }) - } - - val countRunner: CountRunner = { parameters => - val (sql, binder) = parameters.toSql(countQuery = true, namingStrategy = SmartPostgresEscape) - sqlContext - .executeQuery[CountResult]( - sql, - binder, { resultSet => - val count = resultSet.getInt(1) - val lastUpdate = if (parameters.tableData.lastUpdateFieldName.isDefined) { - Option(resultSet.getTimestamp(2)).map(sqlContext.timestampToLocalDateTime) - } else None - - (count, lastUpdate) - } - ) - .head - } - - apply[T]( - tableName = tableName, - lastUpdateFieldName = lastUpdateFieldName, - nullableFields = nullableFields, - links = links, - runner = runner, - countRunner = countRunner - ) - } -} - -class PostgresQueryBuilder[T](parameters: PostgresQueryBuilderParameters)(implicit runner: QueryBuilder.Runner[T], - countRunner: QueryBuilder.CountRunner) - extends QueryBuilder[T, PostgresDialect, PostgresQueryBuilder.Escape](parameters) { - - def withFilter(newFilter: SearchFilterExpr): QueryBuilder[T, PostgresDialect, SmartPostgresEscape] = { - new PostgresQueryBuilder[T](parameters.copy(filter = newFilter)) - } - - def withSorting(newSorting: Sorting): QueryBuilder[T, PostgresDialect, SmartPostgresEscape] = { - new PostgresQueryBuilder[T](parameters.copy(sorting = newSorting)) - } - - def withPagination(newPagination: Pagination): QueryBuilder[T, PostgresDialect, SmartPostgresEscape] = { - new PostgresQueryBuilder[T](parameters.copy(pagination = Some(newPagination))) - } - - def resetPagination: QueryBuilder[T, PostgresDialect, SmartPostgresEscape] = { - new PostgresQueryBuilder[T](parameters.copy(pagination = None)) - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala deleted file mode 100644 index 0bf1ed6..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala +++ /dev/null @@ -1,340 +0,0 @@ -package xyz.driver.pdsuicommon.db - -import java.sql.PreparedStatement -import java.time.LocalDateTime - -import io.getquill.NamingStrategy -import io.getquill.context.sql.idiom.SqlIdiom -import xyz.driver.pdsuicommon.db.Sorting.{Dimension, Sequential} -import xyz.driver.pdsuicommon.db.SortingOrder.{Ascending, Descending} - -import scala.collection.mutable.ListBuffer - -object QueryBuilder { - - type Runner[T] = QueryBuilderParameters => Seq[T] - - type CountResult = (Int, Option[LocalDateTime]) - - type CountRunner = QueryBuilderParameters => CountResult - - /** - * Binder for PreparedStatement - */ - type Binder = PreparedStatement => PreparedStatement - - final case class TableData(tableName: String, - lastUpdateFieldName: Option[String] = None, - nullableFields: Set[String] = Set.empty) - - val AllFields = Set("*") - -} - -final case class TableLink(keyColumnName: String, foreignTableName: String, foreignKeyColumnName: String) - -object QueryBuilderParameters { - val AllFields = Set("*") -} - -sealed trait QueryBuilderParameters { - - def tableData: QueryBuilder.TableData - def links: Map[String, TableLink] - def filter: SearchFilterExpr - def sorting: Sorting - def pagination: Option[Pagination] - - def findLink(tableName: String): TableLink = links.get(tableName) match { - case None => throw new IllegalArgumentException(s"Cannot find a link for `$tableName`") - case Some(link) => link - } - - def toSql(countQuery: Boolean = false, namingStrategy: NamingStrategy): (String, QueryBuilder.Binder) = { - toSql(countQuery, QueryBuilderParameters.AllFields, namingStrategy) - } - - def toSql(countQuery: Boolean, fields: Set[String], namingStrategy: NamingStrategy): (String, QueryBuilder.Binder) = { - val escapedTableName = namingStrategy.table(tableData.tableName) - val fieldsSql: String = if (countQuery) { - val suffix: String = tableData.lastUpdateFieldName match { - case Some(lastUpdateField) => s", max($escapedTableName.${namingStrategy.column(lastUpdateField)})" - case None => "" - } - "count(*)" + suffix - } else { - if (fields == QueryBuilderParameters.AllFields) { - s"$escapedTableName.*" - } else { - fields - .map { field => - s"$escapedTableName.${namingStrategy.column(field)}" - } - .mkString(", ") - } - } - val (where, bindings) = filterToSql(escapedTableName, filter, namingStrategy) - val orderBy = sortingToSql(escapedTableName, sorting, namingStrategy) - - val limitSql = limitToSql() - - val sql = new StringBuilder() - sql.append("select ") - sql.append(fieldsSql) - sql.append("\nfrom ") - sql.append(escapedTableName) - - val filtersTableLinks: Seq[TableLink] = { - import SearchFilterExpr._ - def aux(expr: SearchFilterExpr): Seq[TableLink] = expr match { - case Atom.TableName(tableName) => List(findLink(tableName)) - case Intersection(xs) => xs.flatMap(aux) - case Union(xs) => xs.flatMap(aux) - case _ => Nil - } - aux(filter) - } - - val sortingTableLinks: Seq[TableLink] = Sorting.collect(sorting) { - case Dimension(Some(foreignTableName), _, _) => findLink(foreignTableName) - } - - // Combine links from sorting and filter without duplicates - val foreignTableLinks = (filtersTableLinks ++ sortingTableLinks).distinct - - foreignTableLinks.foreach { - case TableLink(keyColumnName, foreignTableName, foreignKeyColumnName) => - val escapedForeignTableName = namingStrategy.table(foreignTableName) - - sql.append("\ninner join ") - sql.append(escapedForeignTableName) - sql.append(" on ") - - sql.append(escapedTableName) - sql.append('.') - sql.append(namingStrategy.column(keyColumnName)) - - sql.append(" = ") - - sql.append(escapedForeignTableName) - sql.append('.') - sql.append(namingStrategy.column(foreignKeyColumnName)) - } - - if (where.nonEmpty) { - sql.append("\nwhere ") - sql.append(where) - } - - if (orderBy.nonEmpty && !countQuery) { - sql.append("\norder by ") - sql.append(orderBy) - } - - if (limitSql.nonEmpty && !countQuery) { - sql.append("\n") - sql.append(limitSql) - } - - (sql.toString, binder(bindings)) - } - - /** - * Converts filter expression to SQL expression. - * - * @return Returns SQL string and list of values for binding in prepared statement. - */ - protected def filterToSql(escapedTableName: String, - filter: SearchFilterExpr, - namingStrategy: NamingStrategy): (String, List[AnyRef]) = { - import SearchFilterBinaryOperation._ - import SearchFilterExpr._ - - def isNull(string: AnyRef) = Option(string).isEmpty || string.toString.toLowerCase == "null" - - def placeholder(field: String) = "?" - - def escapeDimension(dimension: SearchFilterExpr.Dimension) = { - val tableName = dimension.tableName.fold(escapedTableName)(namingStrategy.table) - s"$tableName.${namingStrategy.column(dimension.name)}" - } - - def filterToSqlMultiple(operands: Seq[SearchFilterExpr]) = operands.collect { - case x if !SearchFilterExpr.isEmpty(x) => filterToSql(escapedTableName, x, namingStrategy) - } - - filter match { - case x if isEmpty(x) => - ("", List.empty) - - case AllowAll => - ("1", List.empty) - - case DenyAll => - ("0", List.empty) - - case Atom.Binary(dimension, Eq, value) if isNull(value) => - (s"${escapeDimension(dimension)} is NULL", List.empty) - - case Atom.Binary(dimension, NotEq, value) if isNull(value) => - (s"${escapeDimension(dimension)} is not NULL", List.empty) - - case Atom.Binary(dimension, NotEq, value) if tableData.nullableFields.contains(dimension.name) => - // In MySQL NULL <> Any === NULL - // So, to handle NotEq for nullable fields we need to use more complex SQL expression. - // http://dev.mysql.com/doc/refman/5.7/en/working-with-null.html - val escapedColumn = escapeDimension(dimension) - val sql = s"($escapedColumn is null or $escapedColumn != ${placeholder(dimension.name)})" - (sql, List(value)) - - case Atom.Binary(dimension, op, value) => - val operator = op match { - case Eq => "=" - case NotEq => "!=" - case Like => "like" - case Gt => ">" - case GtEq => ">=" - case Lt => "<" - case LtEq => "<=" - } - (s"${escapeDimension(dimension)} $operator ${placeholder(dimension.name)}", List(value)) - - case Atom.NAry(dimension, op, values) => - val sqlOp = op match { - case SearchFilterNAryOperation.In => "in" - case SearchFilterNAryOperation.NotIn => "not in" - } - - val bindings = ListBuffer[AnyRef]() - val sqlPlaceholder = placeholder(dimension.name) - val formattedValues = if (values.nonEmpty) { - values - .map { value => - bindings += value - sqlPlaceholder - } - .mkString(", ") - } else "NULL" - (s"${escapeDimension(dimension)} $sqlOp ($formattedValues)", bindings.toList) - - case Intersection(operands) => - val (sql, bindings) = filterToSqlMultiple(operands).unzip - (sql.mkString("(", " and ", ")"), bindings.flatten.toList) - - case Union(operands) => - val (sql, bindings) = filterToSqlMultiple(operands).unzip - (sql.mkString("(", " or ", ")"), bindings.flatten.toList) - } - } - - protected def limitToSql(): String - - /** - * @param escapedMainTableName Should be escaped - */ - protected def sortingToSql(escapedMainTableName: String, sorting: Sorting, namingStrategy: NamingStrategy): String = { - sorting match { - case Dimension(optSortingTableName, field, order) => - val sortingTableName = optSortingTableName.map(namingStrategy.table).getOrElse(escapedMainTableName) - val fullName = s"$sortingTableName.${namingStrategy.column(field)}" - - s"$fullName ${orderToSql(order)}" - - case Sequential(xs) => - xs.map(sortingToSql(escapedMainTableName, _, namingStrategy)).mkString(", ") - } - } - - protected def orderToSql(x: SortingOrder): String = x match { - case Ascending => "asc" - case Descending => "desc" - } - - protected def binder(bindings: List[AnyRef])(bind: PreparedStatement): PreparedStatement = { - bindings.zipWithIndex.foreach { - case (binding, index) => - bind.setObject(index + 1, binding) - } - - bind - } - -} - -final case class PostgresQueryBuilderParameters(tableData: QueryBuilder.TableData, - links: Map[String, TableLink] = Map.empty, - filter: SearchFilterExpr = SearchFilterExpr.Empty, - sorting: Sorting = Sorting.Empty, - pagination: Option[Pagination] = None) - extends QueryBuilderParameters { - - def limitToSql(): String = { - pagination.map { pagination => - val startFrom = (pagination.pageNumber - 1) * pagination.pageSize - s"limit ${pagination.pageSize} OFFSET $startFrom" - } getOrElse "" - } - -} - -/** - * @param links Links to another tables grouped by foreignTableName - */ -final case class MysqlQueryBuilderParameters(tableData: QueryBuilder.TableData, - links: Map[String, TableLink] = Map.empty, - filter: SearchFilterExpr = SearchFilterExpr.Empty, - sorting: Sorting = Sorting.Empty, - pagination: Option[Pagination] = None) - extends QueryBuilderParameters { - - def limitToSql(): String = - pagination - .map { pagination => - val startFrom = (pagination.pageNumber - 1) * pagination.pageSize - s"limit $startFrom, ${pagination.pageSize}" - } - .getOrElse("") - -} - -abstract class QueryBuilder[T, D <: SqlIdiom, N <: NamingStrategy](val parameters: QueryBuilderParameters)( - implicit runner: QueryBuilder.Runner[T], - countRunner: QueryBuilder.CountRunner) { - - def run: Seq[T] = runner(parameters) - - def runCount: QueryBuilder.CountResult = countRunner(parameters) - - /** - * Runs the query and returns total found rows without considering of pagination. - */ - def runWithCount: (Seq[T], Int, Option[LocalDateTime]) = { - val (total, lastUpdate) = runCount - (run, total, lastUpdate) - } - - def withFilter(newFilter: SearchFilterExpr): QueryBuilder[T, D, N] - - def withFilter(filter: Option[SearchFilterExpr]): QueryBuilder[T, D, N] = { - filter.fold(this)(withFilter) - } - - def resetFilter: QueryBuilder[T, D, N] = withFilter(SearchFilterExpr.Empty) - - def withSorting(newSorting: Sorting): QueryBuilder[T, D, N] - - def withSorting(sorting: Option[Sorting]): QueryBuilder[T, D, N] = { - sorting.fold(this)(withSorting) - } - - def resetSorting: QueryBuilder[T, D, N] = withSorting(Sorting.Empty) - - def withPagination(newPagination: Pagination): QueryBuilder[T, D, N] - - def withPagination(pagination: Option[Pagination]): QueryBuilder[T, D, N] = { - pagination.fold(this)(withPagination) - } - - def resetPagination: QueryBuilder[T, D, N] - -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala index 3011f6a..9962edf 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala @@ -83,7 +83,7 @@ sealed trait SlickQueryBuilderParameters { } def toSql(countQuery: Boolean = false)(implicit profile: JdbcProfile): SQLActionBuilder = { - toSql(countQuery, QueryBuilderParameters.AllFields) + toSql(countQuery, SlickQueryBuilderParameters.AllFields) } def toSql(countQuery: Boolean, fields: Set[String])(implicit profile: JdbcProfile): SQLActionBuilder = { diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/repositories/BridgeUploadQueueRepository.scala b/src/main/scala/xyz/driver/pdsuicommon/db/repositories/BridgeUploadQueueRepository.scala deleted file mode 100644 index 4c25afa..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/db/repositories/BridgeUploadQueueRepository.scala +++ /dev/null @@ -1,21 +0,0 @@ -package xyz.driver.pdsuicommon.db.repositories - -import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueue -import xyz.driver.pdsuicommon.db.MysqlQueryBuilder - -trait BridgeUploadQueueRepository extends Repository { - - type EntityT = BridgeUploadQueue.Item - - def add(draft: EntityT): EntityT - - def getById(kind: String, tag: String): Option[EntityT] - - def getOne(kind: String): Option[BridgeUploadQueue.Item] - - def update(entity: EntityT): EntityT - - def delete(kind: String, tag: String): Unit - - def buildQuery: MysqlQueryBuilder[EntityT] -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/repositories/Repository.scala b/src/main/scala/xyz/driver/pdsuicommon/db/repositories/Repository.scala index d671e80..e62238e 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/repositories/Repository.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/repositories/Repository.scala @@ -1,4 +1,3 @@ package xyz.driver.pdsuicommon.db.repositories -// For further usage and migration to Postgres and slick trait Repository extends RepositoryLogging diff --git a/src/main/scala/xyz/driver/pdsuicommon/error/ErrorCode.scala b/src/main/scala/xyz/driver/pdsuicommon/error/ErrorCode.scala index 5574c01..748e76a 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/error/ErrorCode.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/error/ErrorCode.scala @@ -1,7 +1,6 @@ package xyz.driver.pdsuicommon.error -import play.api.libs.functional.syntax._ -import play.api.libs.json.{Format, Reads, Writes} +import xyz.driver.core.json.EnumJsonFormat @SuppressWarnings(Array("org.wartremover.warts.Enumeration")) object ErrorCode extends Enumeration { @@ -9,9 +8,13 @@ object ErrorCode extends Enumeration { type ErrorCode = Value val Unspecified = Value(1) - private val fromJsonReads: Reads[ErrorCode] = Reads.of[Int].map(ErrorCode.apply) - private val toJsonWrites: Writes[ErrorCode] = Writes.of[Int].contramap(_.id) - - implicit val jsonFormat: Format[ErrorCode] = Format(fromJsonReads, toJsonWrites) + implicit val jsonFormat = new EnumJsonFormat[ErrorCode]( + "200" -> ErrorCode.Value(200), + "400" -> ErrorCode.Value(400), + "401" -> ErrorCode.Value(401), + "403" -> ErrorCode.Value(403), + "404" -> ErrorCode.Value(404), + "500" -> ErrorCode.Value(500) + ) } diff --git a/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala b/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala index 3761cc5..ccb84c2 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala @@ -1,18 +1,13 @@ package xyz.driver.pdsuicommon.error -import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat -import ErrorCode.{ErrorCode, Unspecified} +import spray.json._ import ErrorsResponse.ResponseError -import xyz.driver.pdsuicommon.auth.{AnonymousRequestContext, RequestId} -import xyz.driver.pdsuicommon.utils.Utils -import play.api.libs.functional.syntax._ -import play.api.libs.json._ -import play.api.mvc.Results -import xyz.driver.pdsuicommon.validation.JsonValidationErrors +import xyz.driver.pdsuicommon.auth.RequestId final case class ErrorsResponse(errors: Seq[ResponseError], requestId: RequestId) object ErrorsResponse { + import DefaultJsonProtocol._ /** * @param data Any data that can be associated with particular error.Ex.: error field name @@ -21,63 +16,38 @@ object ErrorsResponse { * * @see https://driverinc.atlassian.net/wiki/display/RA/REST+API+Specification#RESTAPISpecification-HTTPStatuscodes */ - final case class ResponseError(data: Option[String], message: String, code: ErrorCode) + final case class ResponseError(data: Option[String], message: String, code: Int) object ResponseError { - implicit val responseErrorJsonFormat: Format[ResponseError] = ( - (JsPath \ "data").formatNullable[String] and - (JsPath \ "message").format[String] and - (JsPath \ "code").format[ErrorCode] - )(ResponseError.apply, unlift(ResponseError.unapply)) + implicit val responseErrorJsonFormat: RootJsonFormat[ResponseError] = jsonFormat3(ResponseError.apply) } - implicit val errorsResponseJsonFormat: Format[ErrorsResponse] = ( - (JsPath \ "errors").format[Seq[ResponseError]] and - (JsPath \ "requestId").format[String] - )((errs, req) => ErrorsResponse.apply(errs, RequestId(req)), res => (res.errors, res.requestId.value)) + implicit val errorsResponseJsonFormat: RootJsonFormat[ErrorsResponse] = new RootJsonFormat[ErrorsResponse] { + override def write(obj: ErrorsResponse): JsValue = { + JsObject( + "errors" -> obj.errors.map(_.toJson).toJson, + "requestId" -> obj.requestId.value.toJson + ) + } - // deprecated, will be removed in REP-436 - def fromString(message: String, httpStatus: Results#Status)( - implicit context: AnonymousRequestContext): ErrorsResponse = { - new ErrorsResponse( - errors = Seq( - ResponseError( - data = None, - message = message, - code = Unspecified - )), - requestId = context.requestId - ) - } + override def read(json: JsValue) = json match { + case JsObject(fields) => + val errors = fields + .get("errors") + .map(_.convertTo[Seq[ResponseError]]) + .getOrElse(deserializationError(s"ErrorsResponse json object does not contain `errors` field: $json")) - // scalastyle:off null - def fromExceptionMessage(e: Throwable, httpStatus: Results#Status = Results.InternalServerError)( - implicit context: AnonymousRequestContext): ErrorsResponse = { - val message = if (e.getMessage == null || e.getMessage.isEmpty) { - Utils.getClassSimpleName(e.getClass) - } else { - e.getMessage - } + val requestId = fields + .get("requestId") + .map(id => RequestId(id.convertTo[String])) + .getOrElse(deserializationError(s"ErrorsResponse json object does not contain `requestId` field: $json")) - fromString(message, httpStatus) - } - // scalastyle:on null + ErrorsResponse(errors, requestId) - // deprecated, will be removed in REP-436 - def fromJsonValidationErrors(validationErrors: JsonValidationErrors)( - implicit context: AnonymousRequestContext): ErrorsResponse = { - val errors = validationErrors.map { - case (path, xs) => - ResponseError( - data = Some(path.toString()), - message = xs.map(_.message).mkString("\n"), - code = Unspecified - ) + case _ => deserializationError(s"Expected json as ErrorsResponse, but got $json") } - - new ErrorsResponse(errors, context.requestId) } } diff --git a/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala b/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala index d7d3df5..4f5cd8a 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala @@ -13,9 +13,8 @@ import xyz.driver.pdsuicommon.error.ErrorsResponse.ResponseError import xyz.driver.pdsuicommon.parsers._ import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting} import xyz.driver.pdsuicommon.domain._ -import xyz.driver.pdsuicommon.serialization.PlayJsonSupport._ +import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import xyz.driver.core.rest.auth.AuthProvider - import scala.util.control._ import scala.util._ @@ -72,7 +71,7 @@ trait Directives { def domainExceptionHandler(req: RequestId): ExceptionHandler = { def errorResponse(ex: Throwable) = - ErrorsResponse(Seq(ResponseError(None, ex.getMessage, ErrorCode.Unspecified)), req) + ErrorsResponse(Seq(ResponseError(None, ex.getMessage, 1)), req) ExceptionHandler { case ex: AuthenticationException => complete(StatusCodes.Unauthorized -> errorResponse(ex)) case ex: AuthorizationException => complete(StatusCodes.Forbidden -> errorResponse(ex)) @@ -84,9 +83,9 @@ trait Directives { def domainRejectionHandler(req: RequestId): RejectionHandler = { def wrapContent(message: String) = { - import play.api.libs.json._ - val err = ErrorsResponse(Seq(ResponseError(None, message, ErrorCode.Unspecified)), req) - val text = Json.stringify(implicitly[Writes[ErrorsResponse]].writes(err)) + import ErrorsResponse._ + val err: ErrorsResponse = ErrorsResponse(Seq(ResponseError(None, message, 1)), req) + val text = errorsResponseJsonFormat.write(err).toString() HttpEntity(ContentTypes.`application/json`, text) } DriverApp.rejectionHandler.mapRejectionResponse { diff --git a/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala b/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala deleted file mode 100644 index 4ff4034..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala +++ /dev/null @@ -1,15 +0,0 @@ -package xyz.driver.pdsuicommon.json - -import play.api.libs.json.JsResult - -import scala.util.{Failure, Success, Try} - -final class JsResultOps[T](val self: JsResult[T]) extends AnyVal { - - def toTry: Try[T] = { - self.fold[Try[T]]( - errors => Failure(new JsonValidationException(errors)), - Success(_) - ) - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/json/JsonValidationException.scala b/src/main/scala/xyz/driver/pdsuicommon/json/JsonValidationException.scala deleted file mode 100644 index 21750b4..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/json/JsonValidationException.scala +++ /dev/null @@ -1,5 +0,0 @@ -package xyz.driver.pdsuicommon.json - -import xyz.driver.pdsuicommon.validation.JsonValidationErrors - -class JsonValidationException(val errors: JsonValidationErrors) extends Exception diff --git a/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala b/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala deleted file mode 100644 index 8231ddb..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala +++ /dev/null @@ -1,46 +0,0 @@ -package xyz.driver.pdsuicommon.json - -import java.net.URI - -import play.api.libs.functional.syntax._ -import play.api.libs.json._ -import xyz.driver.pdsuicommon.domain._ -import xyz.driver.pdsuidomain.entities.CaseId - -object Serialization { - - // @TODO Test and check all items in an array - private def seqJsonReads[T](implicit argFormat: Reads[T]): Reads[Seq[T]] = Reads { - case JsArray(xs) => - JsSuccess(xs.map { x => - argFormat.reads(x).get - }) - case x => JsError(s"Expected JsArray, but got $x") - } - - private def seqJsonWrites[T](implicit argFormat: Writes[T]): Writes[Seq[T]] = Writes { xs => - JsArray(xs.map(argFormat.writes)) - } - - implicit def seqJsonFormat[T](implicit f: Format[T]): Format[Seq[T]] = Format(seqJsonReads[T], seqJsonWrites[T]) - - private val uriJsonReads: Reads[URI] = Reads.StringReads.map(URI.create) - private val uriJsonWrites: Writes[URI] = Writes(uri => JsString(uri.toString)) - implicit val uriJsonFormat: Format[URI] = Format(uriJsonReads, uriJsonWrites) - - private def uuidIdJsonReads[T]: Reads[UuidId[T]] = Reads.uuidReads.map(x => UuidId[T](x)) - private def uuidIdJsonWrites[T]: Writes[UuidId[T]] = Writes.UuidWrites.contramap(_.id) - implicit def uuidIdJsonFormat[T]: Format[UuidId[T]] = Format(uuidIdJsonReads, uuidIdJsonWrites) - - private def longIdJsonReads[T]: Reads[LongId[T]] = Reads.LongReads.map(x => LongId[T](x)) - private def longIdJsonWrites[T]: Writes[LongId[T]] = Writes.LongWrites.contramap(_.id) - implicit def longIdJsonFormat[T]: Format[LongId[T]] = Format(longIdJsonReads, longIdJsonWrites) - - private val emailJsonReads: Reads[Email] = Reads.email.map(Email.apply) - private val emailJsonWrites: Writes[Email] = Writes(email => JsString(email.value)) - implicit val emailJsonFormat: Format[Email] = Format(emailJsonReads, emailJsonWrites) - - private val caseIdJsonReads: Reads[CaseId] = Reads.StringReads.map(CaseId(_)) - private val caseIdJsonWrites: Writes[CaseId] = Writes(caseId => JsString(caseId.id)) - implicit val caseIdJsonFormat: Format[CaseId] = Format(caseIdJsonReads, caseIdJsonWrites) -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala b/src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala deleted file mode 100644 index 5158dab..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala +++ /dev/null @@ -1,34 +0,0 @@ -package xyz.driver.pdsuicommon.serialization - -import akka.http.scaladsl.server.{RejectionError, ValidationRejection} -import akka.http.scaladsl.unmarshalling.Unmarshaller -import play.api.libs.json.{Reads, Writes} -import play.api.libs.json.Json -import akka.http.scaladsl.marshalling.ToEntityMarshaller -import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller -import akka.http.scaladsl.model.MediaTypes.`application/json` - -trait PlayJsonSupport { - import akka.http.scaladsl.marshalling.Marshaller - - implicit def playJsonUnmarshaller[A: Reads]: FromEntityUnmarshaller[A] = { - val reads = implicitly[Reads[A]] - Unmarshaller.stringUnmarshaller - .forContentTypes(`application/json`) - .map(Json.parse) - .map(reads.reads) - .map(_.recoverTotal { error => - throw RejectionError(ValidationRejection(s"Error reading JSON response as ${reads}.")) - }) - } - - implicit def playJsonMarshaller[A: Writes]: ToEntityMarshaller[A] = { - Marshaller - .stringMarshaller(`application/json`) - .compose(Json.prettyPrint) - .compose(implicitly[Writes[A]].writes) - } - -} - -object PlayJsonSupport extends PlayJsonSupport diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala index 34c7495..a79071e 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala @@ -6,16 +6,16 @@ import io.swagger.models.properties.Property import spray.json.JsValue import xyz.driver.pdsuicommon.domain.{LongId, StringId, UuidId} import xyz.driver.pdsuidomain.entities._ -import xyz.driver.pdsuidomain.formats.json.sprayformats.listresponse._ +import xyz.driver.pdsuidomain.formats.json.listresponse._ import xyz.driver.core.swagger.CustomSwaggerJsonConverter._ import xyz.driver.entities.patient.CancerType import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueue import xyz.driver.pdsuidomain.entities.export.patient.ExportPatientWithLabels import xyz.driver.pdsuidomain.entities.export.trial.ExportTrialWithLabels import xyz.driver.pdsuidomain.fakes.entities.common -import xyz.driver.pdsuidomain.formats.json.sprayformats.bridgeuploadqueue._ -import xyz.driver.pdsuidomain.formats.json.sprayformats.record._ -import xyz.driver.pdsuidomain.formats.json.sprayformats.document._ +import xyz.driver.pdsuidomain.formats.json.bridgeuploadqueue._ +import xyz.driver.pdsuidomain.formats.json.record._ +import xyz.driver.pdsuidomain.formats.json.document._ import xyz.driver.pdsuidomain.services.CriterionService.RichCriterion import xyz.driver.pdsuidomain.services.ExtractedDataService.RichExtractedData import xyz.driver.pdsuidomain.services.PatientCriterionService.{DraftPatientCriterion, RichPatientCriterion} @@ -83,17 +83,17 @@ object CustomSwaggerJsonFormats { object trialcuration { import xyz.driver.pdsuidomain.fakes.entities.trialcuration._ import xyz.driver.pdsuidomain.fakes.entities.export - import xyz.driver.pdsuidomain.formats.json.sprayformats.export._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.arm._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.slotarm._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.eligibilityarm._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.criterion._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.intervention._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.hypothesis._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.studydesign._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.trial._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.trialhistory._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.trialissue._ + import xyz.driver.pdsuidomain.formats.json.export._ + import xyz.driver.pdsuidomain.formats.json.arm._ + import xyz.driver.pdsuidomain.formats.json.slotarm._ + import xyz.driver.pdsuidomain.formats.json.eligibilityarm._ + import xyz.driver.pdsuidomain.formats.json.criterion._ + import xyz.driver.pdsuidomain.formats.json.intervention._ + import xyz.driver.pdsuidomain.formats.json.hypothesis._ + import xyz.driver.pdsuidomain.formats.json.studydesign._ + import xyz.driver.pdsuidomain.formats.json.trial._ + import xyz.driver.pdsuidomain.formats.json.trialhistory._ + import xyz.driver.pdsuidomain.formats.json.trialissue._ val customTrialCurationProperties = immutable.Map[Class[_], Property]( classOf[Trial.Status] -> stringProperty(), @@ -102,7 +102,7 @@ object CustomSwaggerJsonFormats { ) ++ customCommonProperties val customTrialCurationObjectsExamples = immutable.Map[Class[_], JsValue]( - classOf[Trial] -> trialWriter.write(nextTrial()), + classOf[Trial] -> trialFormat.write(nextTrial()), classOf[Arm] -> armFormat.write(nextArm()), classOf[TrialHistory] -> trialHistoryFormat.write(nextTrialHistory()), classOf[TrialIssue] -> trialIssueWriter.write(nextTrialIssue()), @@ -134,12 +134,12 @@ object CustomSwaggerJsonFormats { object recordprocessing { import xyz.driver.pdsuidomain.fakes.entities.recordprocessing._ import xyz.driver.pdsuidomain.fakes.entities.export - import xyz.driver.pdsuidomain.formats.json.sprayformats.export._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.documentissue._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.documenthistory._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.recordissue._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.recordhistory._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.extracteddata._ + import xyz.driver.pdsuidomain.formats.json.export._ + import xyz.driver.pdsuidomain.formats.json.documentissue._ + import xyz.driver.pdsuidomain.formats.json.documenthistory._ + import xyz.driver.pdsuidomain.formats.json.recordissue._ + import xyz.driver.pdsuidomain.formats.json.recordhistory._ + import xyz.driver.pdsuidomain.formats.json.extracteddata._ val customRecordProcessingProperties = immutable.Map[Class[_], Property]( classOf[MedicalRecord.Status] -> stringProperty(), @@ -176,14 +176,14 @@ object CustomSwaggerJsonFormats { object treatmentmatching { import xyz.driver.pdsuidomain.fakes.entities.treatmentmatching._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.patient._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.patientcriterion._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.patientdefiningcriteria._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.patienteligibletrial._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.patientlabel._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.patienthypothesis._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.patienthistory._ - import xyz.driver.pdsuidomain.formats.json.sprayformats.patientissue._ + import xyz.driver.pdsuidomain.formats.json.patient._ + import xyz.driver.pdsuidomain.formats.json.patientcriterion._ + import xyz.driver.pdsuidomain.formats.json.patientdefiningcriteria._ + import xyz.driver.pdsuidomain.formats.json.patienteligibletrial._ + import xyz.driver.pdsuidomain.formats.json.patientlabel._ + import xyz.driver.pdsuidomain.formats.json.patienthypothesis._ + import xyz.driver.pdsuidomain.formats.json.patienthistory._ + import xyz.driver.pdsuidomain.formats.json.patientissue._ val customTreatmentMatchingProperties = immutable.Map[Class[_], Property]( classOf[Patient.Status] -> stringProperty(), @@ -192,7 +192,7 @@ object CustomSwaggerJsonFormats { ) ++ customCommonProperties val customTreatmentMatchingObjectsExamples = immutable.Map[Class[_], JsValue]( - classOf[Patient] -> patientWriter.write(nextPatient()), + classOf[Patient] -> patientFormat.write(nextPatient()), classOf[RichPatientLabel] -> richPatientLabelWriter.write(nextRichPatientLabel()), classOf[PatientLabel] -> patientLabelDefiningCriteriaWriter.write(nextPatientLabel()), classOf[RichPatientCriterion] -> patientCriterionWriter.write(nextRichPatientCriterion()), diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/Implicits.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/Implicits.scala index 9411beb..0b18093 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/utils/Implicits.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/utils/Implicits.scala @@ -1,8 +1,5 @@ package xyz.driver.pdsuicommon.utils -import play.api.libs.json.JsResult -import xyz.driver.pdsuicommon.json.JsResultOps - import scala.collection.generic.CanBuildFrom object Implicits { @@ -23,7 +20,6 @@ object Implicits { implicit def toMapOps[K, V](x: Map[K, V]): MapOps[K, V] = new MapOps(x) - implicit def toCharOps(self: Char): CharOps = new CharOps(self) - implicit def toStringOps(self: String): StringOps = new StringOps(self) - implicit def toJsResultOps[T](self: JsResult[T]): JsResultOps[T] = new JsResultOps(self) + implicit def toCharOps(self: Char): CharOps = new CharOps(self) + implicit def toStringOps(self: String): StringOps = new StringOps(self) } diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala deleted file mode 100644 index 6c04dfa..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.driver.pdsuicommon.utils - -import play.api.http.{ContentTypes, Writeable} -import play.api.libs.json.{Json, Writes} - -// @TODO this should be an object with a method, that gets HTTP-headers and returns suitable Writeable -trait WriteableImplicits { - - // Write JSON by default at now - implicit def defaultWriteable[T](implicit inner: Writes[T]) = Writeable[T]( - { x: T => - Writeable.writeableOf_JsValue.transform(Json.toJson(x)) - }, - Option(ContentTypes.JSON) - ) - -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/WritesUtils.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/WritesUtils.scala deleted file mode 100644 index 3476a1e..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/utils/WritesUtils.scala +++ /dev/null @@ -1,21 +0,0 @@ -package xyz.driver.pdsuicommon.utils - -import play.api.libs.json._ - -object WritesUtils { - - def filterKeys[T](p: String => Boolean)(implicit w: Writes[T]): Writes[T] = { - filter { - case (key, _) => p(key) - } - } - - def filter[T](p: (String, JsValue) => Boolean)(implicit w: Writes[T]): Writes[T] = { - w.transform { input: JsValue => - input match { - case JsObject(map) => JsObject(map.filter(Function.tupled(p))) - case x => x - } - } - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/validation/AdditionalConstraints.scala b/src/main/scala/xyz/driver/pdsuicommon/validation/AdditionalConstraints.scala deleted file mode 100644 index cb1082f..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/validation/AdditionalConstraints.scala +++ /dev/null @@ -1,33 +0,0 @@ -package xyz.driver.pdsuicommon.validation - -import org.davidbild.tristate.Tristate -import play.api.data.validation._ - -object AdditionalConstraints { - - val nonNegativePrintedNumber: Constraint[String] = { - Constraints.pattern("^\\d+$".r, "printedInt.nonNegative", "must be a non-negative number") - } - - val positivePrintedNumber: Constraint[String] = { - Constraints.pattern("^[1-9]\\d*$".r, "printedInt.positive", "must be a positive number") - } - - val optionNonEmptyConstraint: Constraint[Option[Any]] = { - Constraint("option.nonEmpty") { - case Some(x) => Valid - case None => Invalid("is empty") - } - } - - val tristateSpecifiedConstraint: Constraint[Tristate[Any]] = { - Constraint("tristate.specified") { - case Tristate.Unspecified => Invalid("unspecified") - case _ => Valid - } - } - - val uuid: Constraint[String] = { - Constraints.pattern("""[\da-z]{8}-[\da-z]{4}-[\da-z]{4}-[\da-z]{4}-[\da-z]{12}""".r, "uuid", "invalid uuid") - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/validation/package.scala b/src/main/scala/xyz/driver/pdsuicommon/validation/package.scala deleted file mode 100644 index 9a31a93..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/validation/package.scala +++ /dev/null @@ -1,8 +0,0 @@ -package xyz.driver.pdsuicommon - -import play.api.data.validation.{ValidationError => PlayValidationError} -import play.api.libs.json.JsPath - -package object validation { - type JsonValidationErrors = Seq[(JsPath, Seq[PlayValidationError])] -} |