aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/pdsuicommon
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/xyz/driver/pdsuicommon')
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala40
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala9
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/computation/FutureToComputationOps.scala14
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/computation/TryToComputationOps.scala12
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueue.scala11
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala48
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/concurrent/InMemoryBridgeUploadQueue.scala17
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala61
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/DbCommand.scala6
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/DbIo.scala13
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/EntityNotFoundException.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala9
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/JdbcDbIo.scala28
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala (renamed from src/main/scala/xyz/driver/pdsuicommon/db/SqlContext.scala)17
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/MysqlQueryBuilder.scala64
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala36
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala5
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/TransactionalContext.scala11
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/Transactions.scala23
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/repositories/BridgeUploadQueueRepository.scala15
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/domain/User.scala33
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/http/AsyncHttpClientFetcher.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala15
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala27
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/logging/TimeLogger.scala (renamed from src/main/scala/xyz/driver/pdsuicommon/TimeLogger.scala)3
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/logging/Unsafe.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/serialization/Marshaller.scala6
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/CharOps.scala31
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/FutureUtils.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/Implicits.scala7
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/StringOps.scala23
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala15
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/WritesUtils.scala21
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/validation/AdditionalConstraints.scala12
34 files changed, 445 insertions, 197 deletions
diff --git a/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala b/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala
index 394e49f..1bb5bcd 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala
@@ -31,7 +31,21 @@ object ACL extends PhiLogging {
object Label
extends BaseACL(
label = "label",
- read = RepRoles ++ TcRoles ++ TreatmentMatchingRoles
+ read = RepRoles ++ TcRoles ++ TreatmentMatchingRoles + ResearchOncologist
+ )
+
+ object UserHistory
+ extends BaseACL(
+ label = "user history",
+ read = Set(RecordAdmin, TrialAdmin, TreatmentMatchingAdmin)
+ )
+
+ object Queue
+ extends BaseACL(
+ label = "queue",
+ create = Set(SystemUser),
+ read = Set(SystemUser),
+ update = Set(SystemUser)
)
// REP
@@ -39,7 +53,7 @@ object ACL extends PhiLogging {
object MedicalRecord
extends BaseACL(
label = "medical record",
- read = RepRoles + RoutesCurator + TreatmentMatchingAdmin,
+ read = RepRoles + RoutesCurator + TreatmentMatchingAdmin + ResearchOncologist,
update = RepRoles - DocumentExtractor
)
@@ -47,7 +61,7 @@ object ACL extends PhiLogging {
extends BaseACL(
label = "document",
create = Set(RecordOrganizer, RecordAdmin),
- read = RepRoles - RecordCleaner + RoutesCurator + TreatmentMatchingAdmin,
+ read = RepRoles - RecordCleaner + RoutesCurator + TreatmentMatchingAdmin + ResearchOncologist,
update = RepRoles - RecordCleaner,
delete = Set(RecordOrganizer, RecordAdmin)
)
@@ -56,7 +70,7 @@ object ACL extends PhiLogging {
extends BaseACL(
label = "extracted data",
create = Set(DocumentExtractor, RecordAdmin),
- read = Set(DocumentExtractor, RecordAdmin, RoutesCurator, TreatmentMatchingAdmin),
+ read = Set(DocumentExtractor, RecordAdmin, RoutesCurator, TreatmentMatchingAdmin, ResearchOncologist),
update = Set(DocumentExtractor, RecordAdmin),
delete = Set(DocumentExtractor, RecordAdmin)
)
@@ -70,13 +84,13 @@ object ACL extends PhiLogging {
object ProviderType
extends BaseACL(
label = "provider type",
- read = RepRoles + RoutesCurator + TreatmentMatchingAdmin
+ read = RepRoles + RoutesCurator + TreatmentMatchingAdmin + ResearchOncologist
)
object DocumentType
extends BaseACL(
label = "document type",
- read = RepRoles + RoutesCurator + TreatmentMatchingAdmin
+ read = RepRoles + RoutesCurator + TreatmentMatchingAdmin + ResearchOncologist
)
object Message
@@ -93,7 +107,7 @@ object ACL extends PhiLogging {
object Trial
extends BaseACL(
label = "trial",
- read = TcRoles + RoutesCurator + TreatmentMatchingAdmin,
+ read = TcRoles + RoutesCurator + TreatmentMatchingAdmin + ResearchOncologist,
update = TcRoles
)
@@ -113,7 +127,7 @@ object ACL extends PhiLogging {
extends BaseACL(
label = "criterion",
create = Set(CriteriaCurator, TrialAdmin),
- read = Set(CriteriaCurator, TrialAdmin, RoutesCurator, TreatmentMatchingAdmin),
+ read = Set(CriteriaCurator, TrialAdmin, RoutesCurator, TreatmentMatchingAdmin, ResearchOncologist),
update = Set(CriteriaCurator, TrialAdmin),
delete = Set(CriteriaCurator, TrialAdmin)
)
@@ -151,34 +165,34 @@ object ACL extends PhiLogging {
object Patient
extends BaseACL(
label = "patient",
- read = TreatmentMatchingRoles,
+ read = TreatmentMatchingRoles + ResearchOncologist,
update = TreatmentMatchingRoles
)
object PatientLabel
extends BaseACL(
label = "patient label",
- read = TreatmentMatchingRoles,
+ read = TreatmentMatchingRoles + ResearchOncologist,
update = TreatmentMatchingRoles
)
object PatientCriterion
extends BaseACL(
label = "patient criterion",
- read = TreatmentMatchingRoles,
+ read = TreatmentMatchingRoles + ResearchOncologist,
update = TreatmentMatchingRoles
)
object PatientLabelEvidence
extends BaseACL(
label = "patient label evidence",
- read = TreatmentMatchingRoles
+ read = TreatmentMatchingRoles + ResearchOncologist
)
object EligibleTrial
extends BaseACL(
label = "eligible trial",
- read = Set(RoutesCurator, TreatmentMatchingAdmin),
+ read = Set(RoutesCurator, TreatmentMatchingAdmin, ResearchOncologist),
update = Set(RoutesCurator, TreatmentMatchingAdmin)
)
diff --git a/src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala b/src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala
index ad458de..159c144 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala
@@ -16,7 +16,7 @@ import scala.concurrent.{ExecutionContext, Future}
* {{{
* import scala.concurrent.ExecutionContext.Implicits.global
* import scala.concurrent.{ExecutionContext, Future}
- * import com.drivergrp.server.com.drivergrp.server.common.utils.Computation
+ * import xyz.driver.pdsuicommon.computation.Computation
*
* def successful = for {
* x <- Computation.continue(1)
@@ -65,6 +65,13 @@ final case class Computation[+R, +T](future: Future[Either[R, T]]) {
Computation.continue(f(a))
}
+ def mapLeft[R2](f: R => R2)(implicit ec: ExecutionContext): Computation[R2, T] = {
+ Computation(future.map {
+ case Left(x) => Left(f(x))
+ case Right(x) => Right(x)
+ })
+ }
+
def andThen(f: T => Any)(implicit ec: ExecutionContext): Computation[R, T] = map { a =>
f(a)
a
diff --git a/src/main/scala/xyz/driver/pdsuicommon/computation/FutureToComputationOps.scala b/src/main/scala/xyz/driver/pdsuicommon/computation/FutureToComputationOps.scala
index c5800dc..6951e79 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/computation/FutureToComputationOps.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/computation/FutureToComputationOps.scala
@@ -6,19 +6,17 @@ import scala.concurrent.{ExecutionContext, Future}
final class FutureToComputationOps[T](val self: Future[T]) extends AnyVal {
- def handleDomainError[U, ER](f: PartialFunction[T, U])
- (implicit unsuitableToErrorsResponse: DomainError => ER,
- ec: ExecutionContext): Future[Either[ER, U]] = {
+ def handleDomainError[U, ER](f: PartialFunction[T, U])(implicit unsuitableToErrorsResponse: DomainError => ER,
+ ec: ExecutionContext): Future[Either[ER, U]] = {
self.map {
case x if f.isDefinedAt(x) => Right(f(x))
- case x: DomainError => Left(unsuitableToErrorsResponse(x))
- case x => throw new RuntimeException(s"Can not process $x")
+ case x: DomainError => Left(unsuitableToErrorsResponse(x))
+ case x => throw new RuntimeException(s"Can not process $x")
}
}
- def toComputation[U, ER](f: PartialFunction[T, U])
- (implicit unsuitableToErrorsResponse: DomainError => ER,
- ec: ExecutionContext): Computation[ER, U] = {
+ def toComputation[U, ER](f: PartialFunction[T, U])(implicit unsuitableToErrorsResponse: DomainError => ER,
+ ec: ExecutionContext): Computation[ER, U] = {
Computation(handleDomainError(f))
}
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/computation/TryToComputationOps.scala b/src/main/scala/xyz/driver/pdsuicommon/computation/TryToComputationOps.scala
index 8282bc6..45f6d41 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/computation/TryToComputationOps.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/computation/TryToComputationOps.scala
@@ -6,10 +6,10 @@ import scala.util.{Failure, Success, Try}
final class TryToComputationOps[T](val self: Try[T]) extends AnyVal {
- def toComputation[ER](implicit exceptionToErrorResponse: Throwable => ER,
- ec: ExecutionContext): Computation[ER, T] = self match {
- case Success(x) => Computation.continue(x)
- case Failure(NonFatal(e)) => Computation.abort(exceptionToErrorResponse(e))
- case Failure(e) => Computation.fail(e)
- }
+ def toComputation[ER](implicit exceptionToErrorResponse: Throwable => ER, ec: ExecutionContext): Computation[ER, T] =
+ self match {
+ case Success(x) => Computation.continue(x)
+ case Failure(NonFatal(e)) => Computation.abort(exceptionToErrorResponse(e))
+ case Failure(e) => Computation.fail(e)
+ }
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueue.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueue.scala
index feb3774..8213262 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueue.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueue.scala
@@ -3,7 +3,6 @@ package xyz.driver.pdsuicommon.concurrent
import java.time.LocalDateTime
import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueue.Item
-import xyz.driver.pdsuicommon.domain.LongId
import xyz.driver.pdsuicommon.logging._
import scala.concurrent.Future
@@ -17,8 +16,7 @@ object BridgeUploadQueue {
* @param created When the task was created
* @param nextAttempt Time of the next attempt
*/
- final case class Item(id: LongId[Item],
- kind: String,
+ final case class Item(kind: String,
tag: String,
created: LocalDateTime,
attempts: Int,
@@ -40,7 +38,7 @@ object BridgeUploadQueue {
implicit def toPhiString(x: Item): PhiString = {
import x._
- phi"BridgeUploadQueue.Item(id=$id, kind=${Unsafe(kind)}, tag=${Unsafe(tag)}, " +
+ phi"BridgeUploadQueue.Item(kind=${Unsafe(kind)}, tag=${Unsafe(tag)}, " +
phi"attempts=${Unsafe(attempts)}, start=$created, nextAttempt=$nextAttempt, completed=$completed, " +
phi"dependency=$dependency)"
}
@@ -49,7 +47,6 @@ object BridgeUploadQueue {
val now = LocalDateTime.now()
Item(
- id = LongId(0),
kind = kind,
tag = tag,
created = now,
@@ -76,11 +73,11 @@ object BridgeUploadQueue {
trait BridgeUploadQueue {
- def add(item: Item): Future[Unit]
+ def add(item: Item): Future[Item]
def get(kind: String): Future[Option[Item]]
- def remove(item: LongId[Item]): Future[Unit]
+ def complete(kind: String, tag: String): Future[Unit]
def tryRetry(item: Item): Future[Option[Item]]
diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala
index 528be59..48c81c2 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala
@@ -5,9 +5,8 @@ import java.time.temporal.ChronoUnit
import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueue.Item
import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueueRepositoryAdapter.Strategy
-import xyz.driver.pdsuicommon.db.Transactions
+import xyz.driver.pdsuicommon.db._
import xyz.driver.pdsuicommon.db.repositories.BridgeUploadQueueRepository
-import xyz.driver.pdsuicommon.domain.LongId
import xyz.driver.pdsuicommon.logging._
import scala.concurrent.duration.{Duration, FiniteDuration}
@@ -16,6 +15,9 @@ 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
@@ -48,9 +50,7 @@ object BridgeUploadQueueRepositoryAdapter {
/**
* Used only in tests.
*/
- case object Ignore extends Strategy {
-
- override val onComplete = OnComplete.Delete
+ final case class Stop(onComplete: OnComplete = OnComplete.Delete) extends Strategy {
override def on(attempt: Int) = OnAttempt.Complete
@@ -85,33 +85,33 @@ object BridgeUploadQueueRepositoryAdapter {
}
}
-class BridgeUploadQueueRepositoryAdapter(strategy: Strategy,
- repository: BridgeUploadQueueRepository,
- transactions: Transactions)(implicit executionContext: ExecutionContext)
+class BridgeUploadQueueRepositoryAdapter(strategy: Strategy, repository: BridgeUploadQueueRepository, dbIo: DbIo)(
+ implicit executionContext: ExecutionContext)
extends BridgeUploadQueue with PhiLogging {
- override def add(item: Item): Future[Unit] = transactions.run { _ =>
- repository.add(item)
- }
+ override def add(item: Item): Future[Item] = dbIo.runAsync(repository.add(item))
- override def get(kind: String): Future[Option[Item]] = {
- repository.getOne(kind)
- }
+ override def get(kind: String): Future[Option[Item]] = dbIo.runAsync(repository.getOne(kind))
- override def remove(item: LongId[Item]): Future[Unit] = transactions.run { _ =>
+ override def complete(kind: String, tag: String): Future[Unit] = {
import Strategy.OnComplete._
strategy.onComplete match {
- case Delete => repository.delete(item)
+ case Delete => dbIo.runAsync(repository.delete(kind, tag))
case Mark =>
- repository.getById(item) match {
- case Some(x) => repository.update(x.copy(completed = true))
- case None => throw new RuntimeException(s"Can not find the $item task")
+ 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")
+ }
}
}
}
- override def tryRetry(item: Item): Future[Option[Item]] = transactions.run { _ =>
+ /**
+ * Tries to continue the task or complete it
+ */
+ override def tryRetry(item: Item): Future[Option[Item]] = {
import Strategy.OnAttempt._
logger.trace(phi"tryRetry($item)")
@@ -128,11 +128,13 @@ class BridgeUploadQueueRepositoryAdapter(strategy: Strategy,
)
logger.debug(draftItem)
- Some(repository.update(draftItem))
+ dbIo.runAsync {
+ Some(repository.update(draftItem))
+ }
case Complete =>
- repository.delete(item.id)
- None
+ 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/concurrent/InMemoryBridgeUploadQueue.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/InMemoryBridgeUploadQueue.scala
index bff566b..658b5b1 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/InMemoryBridgeUploadQueue.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/InMemoryBridgeUploadQueue.scala
@@ -1,9 +1,9 @@
package xyz.driver.pdsuicommon.concurrent
import java.util.concurrent.LinkedBlockingQueue
+import java.util.function.Predicate
import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueue.Item
-import xyz.driver.pdsuicommon.domain.LongId
import xyz.driver.pdsuicommon.logging.PhiLogging
import scala.collection.JavaConverters._
@@ -16,9 +16,9 @@ class InMemoryBridgeUploadQueue extends BridgeUploadQueue with PhiLogging {
private val queue = new LinkedBlockingQueue[Item]()
- override def add(item: Item): Future[Unit] = {
+ override def add(item: Item): Future[Item] = {
queue.add(item)
- done
+ Future.successful(item)
}
override def tryRetry(item: Item): Future[Option[Item]] = Future.successful(Some(item))
@@ -28,11 +28,10 @@ class InMemoryBridgeUploadQueue extends BridgeUploadQueue with PhiLogging {
Future.successful(r)
}
- override def remove(item: LongId[Item]): Future[Unit] = {
- queue.remove(item)
- done
+ override def complete(kind: String, tag: String): Future[Unit] = {
+ queue.removeIf(new Predicate[Item] {
+ override def test(t: Item): Boolean = t.kind == kind && t.tag == tag
+ })
+ Future.successful(())
}
-
- private val done = Future.successful(())
-
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala
new file mode 100644
index 0000000..bab29d5
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala
@@ -0,0 +1,61 @@
+package xyz.driver.pdsuicommon.concurrent
+
+import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueue.Dependency
+import xyz.driver.pdsuicommon.concurrent.SafeBridgeUploadQueue.{DependencyResolver, SafeTask, Tag}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuicommon.serialization.Marshaller
+
+import scala.concurrent.{ExecutionContext, Future}
+
+object SafeBridgeUploadQueue {
+
+ trait Tag extends Product with Serializable
+
+ case class SafeTask[T <: Tag](tag: T,
+ private[SafeBridgeUploadQueue] val queueItem: BridgeUploadQueue.Item)
+
+ object SafeTask {
+ implicit def toPhiString[T <: Tag](x: SafeTask[T]): PhiString = {
+ import x._
+ phi"SafeTask(tag=${Unsafe(tag)}, $queueItem)"
+ }
+ }
+
+ trait DependencyResolver[T <: Tag] {
+ def getDependency(tag: T): Option[Dependency]
+ }
+
+}
+
+class SafeBridgeUploadQueue[T <: Tag](kind: String,
+ origQueue: BridgeUploadQueue)
+ (implicit
+ tagMarshaller: Marshaller[T, String],
+ dependencyResolver: DependencyResolver[T],
+ executionContext: ExecutionContext) {
+
+ type Task = SafeTask[T]
+
+ def add(tag: T): Future[BridgeUploadQueue.Item] = origQueue.add(BridgeUploadQueue.Item(
+ kind = kind,
+ tag = tagMarshaller.write(tag),
+ dependency = dependencyResolver.getDependency(tag)
+ ))
+
+ def tryRetry(task: Task): Future[Option[Task]] = wrap(origQueue.tryRetry(task.queueItem))
+
+ def get: Future[Option[Task]] = wrap(origQueue.get(kind))
+
+ def complete(tag: T): Future[Unit] = origQueue.complete(kind, tagMarshaller.write(tag))
+
+ private def wrap(x: Future[Option[BridgeUploadQueue.Item]]): Future[Option[Task]] = x.map(_.map(cover))
+
+ private def cover(rawTask: BridgeUploadQueue.Item): Task = {
+ val tag = tagMarshaller
+ .read(rawTask.tag)
+ .getOrElse(throw new IllegalArgumentException(s"Can not parse tag '${rawTask.tag}'"))
+
+ SafeTask(tag, rawTask)
+ }
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/DbCommand.scala b/src/main/scala/xyz/driver/pdsuicommon/db/DbCommand.scala
index 5dafc00..0af104e 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/DbCommand.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/DbCommand.scala
@@ -4,12 +4,12 @@ import scala.concurrent.Future
trait DbCommand {
def runSync(): Unit
- def runAsync(transactions: Transactions): Future[Unit]
+ def runAsync(transactions: DbIo): Future[Unit]
}
object DbCommand {
val Empty: DbCommand = new DbCommand {
- override def runSync(): Unit = {}
- override def runAsync(transactions: Transactions): Future[Unit] = Future.successful(())
+ override def runSync(): Unit = {}
+ override def runAsync(transactions: DbIo): Future[Unit] = Future.successful(())
}
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/DbIo.scala b/src/main/scala/xyz/driver/pdsuicommon/db/DbIo.scala
new file mode 100644
index 0000000..7c290d1
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/DbIo.scala
@@ -0,0 +1,13 @@
+package xyz.driver.pdsuicommon.db
+
+import scala.concurrent.Future
+
+/**
+ * Where queries should run
+ */
+trait DbIo {
+ def runAsync[T](f: => T): Future[T]
+ def runSync[T](f: => T): T = f
+ def runAsyncTx[T](f: => T): Future[T]
+ def runSyncTx[T](f: => T): Unit
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/EntityNotFoundException.scala b/src/main/scala/xyz/driver/pdsuicommon/db/EntityNotFoundException.scala
index d779e10..d765833 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/EntityNotFoundException.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/EntityNotFoundException.scala
@@ -2,7 +2,7 @@ package xyz.driver.pdsuicommon.db
import xyz.driver.pdsuicommon.domain.Id
-class EntityNotFoundException private (id: String, tableName: String)
+class EntityNotFoundException(id: String, tableName: String)
extends RuntimeException(s"Entity with id $id is not found in $tableName table") {
def this(id: Id[_], tableName: String) = this(id.toString, tableName)
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala b/src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala
new file mode 100644
index 0000000..e5a628c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala
@@ -0,0 +1,9 @@
+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/JdbcDbIo.scala b/src/main/scala/xyz/driver/pdsuicommon/db/JdbcDbIo.scala
new file mode 100644
index 0000000..44f177c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/JdbcDbIo.scala
@@ -0,0 +1,28 @@
+package xyz.driver.pdsuicommon.db
+
+import xyz.driver.pdsuicommon.logging._
+
+import scala.concurrent.Future
+import scala.util.{Failure, Success, Try}
+
+class JdbcDbIo(sqlContext: TransactionalContext) extends DbIo with PhiLogging {
+
+ override def runAsync[T](f: => T): Future[T] = {
+ Future(f)(sqlContext.executionContext)
+ }
+
+ override def runAsyncTx[T](f: => T): Future[T] = {
+ import sqlContext.executionContext
+
+ Future(sqlContext.transaction(f)).andThen {
+ case Failure(e) => logger.error(phi"Can't run a transaction: $e")
+ }
+ }
+
+ override def runSyncTx[T](f: => T): Unit = {
+ Try(sqlContext.transaction(f)) match {
+ case Success(_) =>
+ case Failure(e) => logger.error(phi"Can't run a transaction: $e")
+ }
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SqlContext.scala b/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala
index c929eae..768d1e3 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/SqlContext.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala
@@ -8,15 +8,15 @@ import javax.sql.DataSource
import com.typesafe.config.Config
import io.getquill._
import xyz.driver.pdsuicommon.concurrent.MdcExecutionContext
-import xyz.driver.pdsuicommon.db.SqlContext.Settings
+import xyz.driver.pdsuicommon.db.MySqlContext.Settings
import xyz.driver.pdsuicommon.error.IncorrectIdException
-import xyz.driver.pdsuicommon.logging.{PhiLogging, Unsafe}
+import xyz.driver.pdsuicommon.logging._
import scala.concurrent.ExecutionContext
import scala.util.control.NonFatal
import scala.util.{Failure, Success, Try}
-object SqlContext extends PhiLogging {
+object MySqlContext extends PhiLogging {
case class DbCredentials(user: String,
password: String,
@@ -33,20 +33,21 @@ object SqlContext extends PhiLogging {
connectionAttemptsOnStartup: Int,
threadPoolSize: Int)
- def apply(settings: Settings): SqlContext = {
+ def apply(settings: Settings): MySqlContext = {
// Prevent leaking credentials to a log
Try(JdbcContextConfig(settings.connection).dataSource) match {
- case Success(dataSource) => new SqlContext(dataSource, settings)
+ 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 SqlContext(dataSource: DataSource with Closeable, settings: Settings)
- extends MysqlJdbcContext[MysqlEscape](dataSource) with EntityExtractorDerivation[Literal] {
+class MySqlContext(dataSource: DataSource with Closeable, settings: Settings)
+ extends MysqlJdbcContext[MysqlEscape](dataSource)
+ with TransactionalContext
+ with EntityExtractorDerivation[Literal] {
private val tpe = Executors.newFixedThreadPool(settings.threadPoolSize)
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/MysqlQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/MysqlQueryBuilder.scala
index 6b7639a..e2936e3 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/MysqlQueryBuilder.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/MysqlQueryBuilder.scala
@@ -2,12 +2,12 @@ package xyz.driver.pdsuicommon.db
import java.sql.ResultSet
+import xyz.driver.pdsuicommon.logging._
import io.getquill.{MySQLDialect, MysqlEscape}
import scala.collection.breakOut
-import scala.concurrent.{ExecutionContext, Future}
-object MysqlQueryBuilder {
+object MysqlQueryBuilder extends PhiLogging {
import xyz.driver.pdsuicommon.db.QueryBuilder._
def apply[T](tableName: String,
@@ -15,46 +15,44 @@ object MysqlQueryBuilder {
nullableFields: Set[String],
links: Set[TableLink],
runner: Runner[T],
- countRunner: CountRunner)(implicit ec: ExecutionContext): MysqlQueryBuilder[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, ec)
+ 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: SqlContext): MysqlQueryBuilder[T] = {
-
- val runner = (parameters: QueryBuilderParameters) => {
- Future {
- val (sql, binder) = parameters.toSql(namingStrategy = MysqlEscape)
- sqlContext.executeQuery[T](sql, binder, { resultSet =>
- extractor(resultSet)
- })
- }(sqlContext.executionContext)
+ 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 = (parameters: QueryBuilderParameters) => {
- Future {
- val (sql, binder) = parameters.toSql(countQuery = true, namingStrategy = MysqlEscape)
- 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
- }(sqlContext.executionContext)
+ 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](
@@ -64,13 +62,12 @@ object MysqlQueryBuilder {
links = links,
runner = runner,
countRunner = countRunner
- )(sqlContext.executionContext)
+ )
}
}
class MysqlQueryBuilder[T](parameters: MysqlQueryBuilderParameters)(implicit runner: QueryBuilder.Runner[T],
- countRunner: QueryBuilder.CountRunner,
- ec: ExecutionContext)
+ countRunner: QueryBuilder.CountRunner)
extends QueryBuilder[T, MySQLDialect, MysqlEscape](parameters) {
def withFilter(newFilter: SearchFilterExpr): QueryBuilder[T, MySQLDialect, MysqlEscape] = {
@@ -88,5 +85,4 @@ class MysqlQueryBuilder[T](parameters: MysqlQueryBuilderParameters)(implicit run
def resetPagination: QueryBuilder[T, MySQLDialect, MysqlEscape] = {
new MysqlQueryBuilder[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
index 733d355..f941627 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala
@@ -9,15 +9,14 @@ import xyz.driver.pdsuicommon.db.Sorting.{Dimension, Sequential}
import xyz.driver.pdsuicommon.db.SortingOrder.{Ascending, Descending}
import scala.collection.mutable.ListBuffer
-import scala.concurrent.{ExecutionContext, Future}
object QueryBuilder {
- type Runner[T] = (QueryBuilderParameters) => Future[Seq[T]]
+ type Runner[T] = QueryBuilderParameters => Seq[T]
type CountResult = (Int, Option[LocalDateTime])
- type CountRunner = (QueryBuilderParameters) => Future[CountResult]
+ type CountRunner = QueryBuilderParameters => CountResult
/**
* Binder for PreparedStatement
@@ -207,12 +206,14 @@ sealed trait QueryBuilderParameters {
val bindings = ListBuffer[AnyRef]()
val sqlPlaceholder = placeholder(dimension.name)
- val formattedValues = values
- .map { value =>
- bindings += value
- sqlPlaceholder
- }
- .mkString(", ")
+ val formattedValues = if (values.nonEmpty) {
+ values
+ .map { value =>
+ bindings += value
+ sqlPlaceholder
+ }
+ .mkString(", ")
+ } else "NULL"
(s"${escapeDimension(dimension)} $sqlOp ($formattedValues)", bindings.toList)
case Intersection(operands) =>
@@ -297,23 +298,18 @@ case class MysqlQueryBuilderParameters(tableData: QueryBuilder.TableData,
abstract class QueryBuilder[T, D <: SqlIdiom, N <: NamingStrategy](val parameters: QueryBuilderParameters)(
implicit runner: QueryBuilder.Runner[T],
- countRunner: QueryBuilder.CountRunner,
- ec: ExecutionContext) {
+ countRunner: QueryBuilder.CountRunner) {
- def run: Future[Seq[T]] = runner(parameters)
+ def run: Seq[T] = runner(parameters)
- def runCount: Future[QueryBuilder.CountResult] = countRunner(parameters)
+ def runCount: QueryBuilder.CountResult = countRunner(parameters)
/**
* Runs the query and returns total found rows without considering of pagination.
*/
- def runWithCount: Future[(Seq[T], Int, Option[LocalDateTime])] = {
- val countFuture = runCount
- val selectAllFuture = run
- for {
- (total, lastUpdate) <- countFuture
- all <- selectAllFuture
- } yield (all, total, lastUpdate)
+ def runWithCount: (Seq[T], Int, Option[LocalDateTime]) = {
+ val (total, lastUpdate) = runCount
+ (run, total, lastUpdate)
}
def withFilter(newFilter: SearchFilterExpr): QueryBuilder[T, D, N]
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala
index 5144163..4b66f22 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala
@@ -54,7 +54,7 @@ object SearchFilterExpr {
}
}
- case class Intersection private (operands: Seq[SearchFilterExpr])
+ final case class Intersection private (operands: Seq[SearchFilterExpr])
extends SearchFilterExpr with SearchFilterExprSeqOps {
override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = {
@@ -80,7 +80,8 @@ object SearchFilterExpr {
}
}
- case class Union private (operands: Seq[SearchFilterExpr]) extends SearchFilterExpr with SearchFilterExprSeqOps {
+ final case class Union private (operands: Seq[SearchFilterExpr])
+ extends SearchFilterExpr with SearchFilterExprSeqOps {
override def replace(f: PartialFunction[SearchFilterExpr, SearchFilterExpr]): SearchFilterExpr = {
if (f.isDefinedAt(this)) f(this)
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/TransactionalContext.scala b/src/main/scala/xyz/driver/pdsuicommon/db/TransactionalContext.scala
new file mode 100644
index 0000000..9883b9e
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/TransactionalContext.scala
@@ -0,0 +1,11 @@
+package xyz.driver.pdsuicommon.db
+
+import scala.concurrent.ExecutionContext
+
+trait TransactionalContext {
+
+ implicit def executionContext: ExecutionContext
+
+ def transaction[T](f: => T): T
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/Transactions.scala b/src/main/scala/xyz/driver/pdsuicommon/db/Transactions.scala
deleted file mode 100644
index 72c358a..0000000
--- a/src/main/scala/xyz/driver/pdsuicommon/db/Transactions.scala
+++ /dev/null
@@ -1,23 +0,0 @@
-package xyz.driver.pdsuicommon.db
-
-import xyz.driver.pdsuicommon.logging.PhiLogging
-
-import scala.concurrent.Future
-import scala.util.{Failure, Success, Try}
-
-class Transactions()(implicit context: SqlContext) extends PhiLogging {
- def run[T](f: SqlContext => T): Future[T] = {
- import context.executionContext
-
- Future(context.transaction(f(context))).andThen {
- case Failure(e) => logger.error(phi"Can't run a transaction: $e")
- }
- }
-
- def runSync[T](f: SqlContext => T): Unit = {
- Try(context.transaction(f(context))) match {
- case Success(_) =>
- case Failure(e) => logger.error(phi"Can't run a transaction: $e")
- }
- }
-}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/repositories/BridgeUploadQueueRepository.scala b/src/main/scala/xyz/driver/pdsuicommon/db/repositories/BridgeUploadQueueRepository.scala
index a3140a6..4c25afa 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/repositories/BridgeUploadQueueRepository.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/repositories/BridgeUploadQueueRepository.scala
@@ -1,24 +1,21 @@
package xyz.driver.pdsuicommon.db.repositories
import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueue
-import xyz.driver.pdsuicommon.domain.LongId
-
-import scala.concurrent.Future
+import xyz.driver.pdsuicommon.db.MysqlQueryBuilder
trait BridgeUploadQueueRepository extends Repository {
type EntityT = BridgeUploadQueue.Item
- type IdT = LongId[EntityT]
def add(draft: EntityT): EntityT
- def getById(id: LongId[EntityT]): Option[EntityT]
-
- def isCompleted(kind: String, tag: String): Future[Boolean]
+ def getById(kind: String, tag: String): Option[EntityT]
- def getOne(kind: String): Future[Option[BridgeUploadQueue.Item]]
+ def getOne(kind: String): Option[BridgeUploadQueue.Item]
def update(entity: EntityT): EntityT
- def delete(id: IdT): Unit
+ def delete(kind: String, tag: String): Unit
+
+ def buildQuery: MysqlQueryBuilder[EntityT]
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala
index 45adefc..bf4970e 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala
@@ -1,5 +1,7 @@
package xyz.driver.pdsuicommon.domain
+import java.math.BigInteger
+import java.security.SecureRandom
import java.time.LocalDateTime
import xyz.driver.pdsuicommon.logging._
@@ -31,16 +33,18 @@ object User {
}
object Role extends PhiLogging {
- case object RecordAdmin extends Role { val bit = 1 << 0 }
- case object RecordCleaner extends Role { val bit = 1 << 1 }
- case object RecordOrganizer extends Role { val bit = 1 << 2 }
- case object DocumentExtractor extends Role { val bit = 1 << 3 }
- case object TrialSummarizer extends Role { val bit = 1 << 4 }
- case object CriteriaCurator extends Role { val bit = 1 << 5 }
- case object TrialAdmin extends Role { val bit = 1 << 6 }
- case object EligibilityVerifier extends Role { val bit = 1 << 7 }
- case object TreatmentMatchingAdmin extends Role { val bit = 1 << 8 }
- case object RoutesCurator extends Role { val bit = 1 << 9 }
+ case object RecordAdmin extends Role { val bit = 1 << 0 }
+ case object RecordCleaner extends Role { val bit = 1 << 1 }
+ case object RecordOrganizer extends Role { val bit = 1 << 2 }
+ case object DocumentExtractor extends Role { val bit = 1 << 3 }
+ case object TrialSummarizer extends Role { val bit = 1 << 4 }
+ case object CriteriaCurator extends Role { val bit = 1 << 5 }
+ case object TrialAdmin extends Role { val bit = 1 << 6 }
+ case object EligibilityVerifier extends Role { val bit = 1 << 7 }
+ case object TreatmentMatchingAdmin extends Role { val bit = 1 << 8 }
+ case object RoutesCurator extends Role { val bit = 1 << 9 }
+ case object SystemUser extends Role { val bit = 1 << 10 }
+ case object ResearchOncologist extends Role { val bit = 1 << 11 }
val RepRoles = Set[Role](RecordAdmin, RecordCleaner, RecordOrganizer, DocumentExtractor)
@@ -48,7 +52,9 @@ object User {
val TreatmentMatchingRoles = Set[Role](RoutesCurator, EligibilityVerifier, TreatmentMatchingAdmin)
- val All = RepRoles ++ TcRoles ++ TreatmentMatchingRoles
+ val PepRoles = Set[Role](ResearchOncologist)
+
+ val All = RepRoles ++ TcRoles ++ TreatmentMatchingRoles ++ PepRoles + SystemUser
def apply(bitMask: Int): Role = {
def extractRole(role: Role): Set[Role] =
@@ -71,4 +77,9 @@ object User {
phi"User(id=$id, role=$role)"
}
+ // SecureRandom is thread-safe, see the implementation
+ private val random = new SecureRandom()
+
+ def createPassword: String = new BigInteger(240, random).toString(32)
+
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/http/AsyncHttpClientFetcher.scala b/src/main/scala/xyz/driver/pdsuicommon/http/AsyncHttpClientFetcher.scala
index d836b9d..085dcd8 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/http/AsyncHttpClientFetcher.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/http/AsyncHttpClientFetcher.scala
@@ -85,6 +85,6 @@ class AsyncHttpClientFetcher(settings: AsyncHttpClientFetcher.Settings)
object AsyncHttpClientFetcher {
- case class Settings(connectTimeout: FiniteDuration, readTimeout: FiniteDuration)
+ final case class Settings(connectTimeout: FiniteDuration, readTimeout: FiniteDuration)
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala b/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala
new file mode 100644
index 0000000..07dfefc
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala
@@ -0,0 +1,15 @@
+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(
+ errors => Failure(new JsonValidationException(errors)),
+ Success(_)
+ )
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala b/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala
index a6d3ee9..223c66e 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala
@@ -10,7 +10,10 @@ 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 JsArray(xs) =>
+ JsSuccess(xs.map { x =>
+ argFormat.reads(x).get
+ })
case x => JsError(s"Expected JsArray, but got $x")
}
@@ -20,23 +23,25 @@ object Serialization {
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))
+ 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)
+ 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)
+ 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))
+ 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 passwordHashJsonReads: Reads[PasswordHash] = Reads.StringReads.map(hash => PasswordHash(hash.getBytes("UTF-8")))
- private val passwordHashJsonWrites: Writes[PasswordHash] = Writes(passwordHash => JsString(passwordHash.value.toString))
+ private val passwordHashJsonReads: Reads[PasswordHash] =
+ Reads.StringReads.map(hash => PasswordHash(hash.getBytes("UTF-8")))
+ private val passwordHashJsonWrites: Writes[PasswordHash] = Writes(
+ passwordHash => JsString(passwordHash.value.toString))
implicit val passwordHashJsonFormat: Format[PasswordHash] = Format(passwordHashJsonReads, passwordHashJsonWrites)
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/TimeLogger.scala b/src/main/scala/xyz/driver/pdsuicommon/logging/TimeLogger.scala
index 41c83d5..dd5ba5e 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/TimeLogger.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/logging/TimeLogger.scala
@@ -1,9 +1,8 @@
-package xyz.driver.pdsuicommon
+package xyz.driver.pdsuicommon.logging
import java.time.{LocalDateTime, ZoneId}
import xyz.driver.pdsuicommon.domain.{LongId, User}
-import xyz.driver.pdsuicommon.logging._
object TimeLogger extends PhiLogging {
diff --git a/src/main/scala/xyz/driver/pdsuicommon/logging/Unsafe.scala b/src/main/scala/xyz/driver/pdsuicommon/logging/Unsafe.scala
index 7fd810f..c3ebe80 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/logging/Unsafe.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/logging/Unsafe.scala
@@ -3,5 +3,5 @@ package xyz.driver.pdsuicommon.logging
/**
* Use it with care!
*/
-case class Unsafe[T](private[logging] val value: T)
+final case class Unsafe[T](private[logging] val value: T)
extends PhiString(Option(value).map(_.toString).getOrElse("<null>"))
diff --git a/src/main/scala/xyz/driver/pdsuicommon/serialization/Marshaller.scala b/src/main/scala/xyz/driver/pdsuicommon/serialization/Marshaller.scala
new file mode 100644
index 0000000..6702de2
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/serialization/Marshaller.scala
@@ -0,0 +1,6 @@
+package xyz.driver.pdsuicommon.serialization
+
+trait Marshaller[T, Repr] {
+ def read(x: Repr): Option[T]
+ def write(x: T): Repr
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/CharOps.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/CharOps.scala
new file mode 100644
index 0000000..42bf92d
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/CharOps.scala
@@ -0,0 +1,31 @@
+package xyz.driver.pdsuicommon.utils
+
+final class CharOps(val self: Char) extends AnyVal {
+
+ import CharOps._
+
+ def isSafeWhitespace: Boolean = Whitespace.matches(self)
+
+ def isSafeControl: Boolean = JavaIsoControl.matches(self)
+}
+
+// From Guava
+private object CharOps {
+
+ object Whitespace {
+ private val Table: String =
+ "\u2002\u3000\r\u0085\u200A\u2005\u2000\u3000" +
+ "\u2029\u000B\u3000\u2008\u2003\u205F\u3000\u1680" +
+ "\u0009\u0020\u2006\u2001\u202F\u00A0\u000C\u2009" +
+ "\u3000\u2004\u3000\u3000\u2028\n\u2007\u3000"
+
+ private val Multiplier: Int = 1682554634
+ private val Shift: Int = Integer.numberOfLeadingZeros(Table.length - 1)
+
+ def matches(c: Char): Boolean = Table.charAt((Multiplier * c) >>> Shift) == c
+ }
+
+ object JavaIsoControl {
+ def matches(c: Char): Boolean = c <= '\u001f' || (c >= '\u007f' && c <= '\u009f')
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/FutureUtils.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/FutureUtils.scala
index 9eecb7f..e8b1f5c 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/utils/FutureUtils.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/FutureUtils.scala
@@ -1,7 +1,7 @@
package xyz.driver.pdsuicommon.utils
import scala.concurrent.{ExecutionContext, Future}
-import scala.util.Try
+import scala.util.{Failure, Try}
object FutureUtils {
@@ -13,6 +13,6 @@ object FutureUtils {
override def execute(runnable: Runnable): Unit = runnable.run()
}
}
- future.value.get
+ future.value.getOrElse(Failure(new IllegalStateException("Can not evaluate the result of future")))
}
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/Implicits.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/Implicits.scala
index c8af125..9411beb 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/utils/Implicits.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/Implicits.scala
@@ -1,5 +1,8 @@
package xyz.driver.pdsuicommon.utils
+import play.api.libs.json.JsResult
+import xyz.driver.pdsuicommon.json.JsResultOps
+
import scala.collection.generic.CanBuildFrom
object Implicits {
@@ -19,4 +22,8 @@ object Implicits {
new ConditionalAppend[U, T](c)
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)
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/StringOps.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/StringOps.scala
new file mode 100644
index 0000000..b38721e
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/StringOps.scala
@@ -0,0 +1,23 @@
+package xyz.driver.pdsuicommon.utils
+
+import xyz.driver.pdsuicommon.utils.Implicits.toCharOps
+
+final class StringOps(val self: String) extends AnyVal {
+
+ def safeTrim: String = {
+ def shouldKeep(c: Char): Boolean = !c.isSafeControl && !c.isSafeWhitespace
+
+ if (self.isEmpty) {
+ ""
+ } else {
+ val start = self.indexWhere(shouldKeep)
+ val end = self.lastIndexWhere(shouldKeep)
+
+ if (start >= 0 && end >= 0) {
+ self.substring(start, end + 1)
+ } else {
+ ""
+ }
+ }
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala
new file mode 100644
index 0000000..2c66a23
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala
@@ -0,0 +1,15 @@
+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
new file mode 100644
index 0000000..fa05e96
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/WritesUtils.scala
@@ -0,0 +1,21 @@
+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
index 115163c..cb1082f 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/validation/AdditionalConstraints.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/validation/AdditionalConstraints.scala
@@ -5,17 +5,25 @@ 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")
+ case None => Invalid("is empty")
}
}
val tristateSpecifiedConstraint: Constraint[Tristate[Any]] = {
Constraint("tristate.specified") {
case Tristate.Unspecified => Invalid("unspecified")
- case _ => Valid
+ case _ => Valid
}
}